Better tests structure.

Better tests structure.

--- test/tests.coffee
+++ test/tests.coffee
@@ -16,10 +16,13 @@
   states  = [ 'memory' , 'redis' ]

   makeParams = (userName) ->
-    return query : "user=#{userName}"
+    q = 'query' : "user=#{userName}"
     , 'multiplex' : false
     , 'reconnection' : false
     , 'transports' : [ 'websocket' ]
+    unless userName
+      delete q.query
+    return q

   user1 = 'userName1'
   user2 = 'userName2'
@@ -30,527 +33,507 @@
   url1 = "http://localhost:#{port}/chat-service"

   redis = new Redis
-
   chatServer = null
   socket1 = null
   socket2 = null
   socket3 = null

+  afterEachFn = (done) ->
+    endcb = -> redis.flushall done
+    if socket1
+      socket1.disconnect()
+      socket1 = null
+    if socket2
+      socket2.disconnect()
+      socket2 = null
+    if socket3
+      socket3.disconnect()
+      socket3 = null
+    if chatServer
+      chatServer.close endcb
+      chatServer = null
+    else
+      endcb()
+
+  before (done) ->
+    redis.dbsize (error, data) ->
+      if error then return done error
+      if data then return done new Error 'Unclean Redis DB'
+      done()
+
   states.forEach (state) ->

-    beforeFn = (done) ->
-      redis.dbsize (error, data) ->
-        if error then return done error
-        if data then return done new Error 'Unclean Redis DB'
-        done()
-
-    afterEachFn = (done) ->
-      endcb = -> redis.flushall done
-      if socket1
-        socket1.disconnect()
-        socket1 = null
-      if socket2
-        socket2.disconnect()
-        socket2 = null
-      if socket3
-        socket3.disconnect()
-        socket3 = null
-      if chatServer
-        chatServer.close endcb
-        chatServer = null
-      else
-        endcb()
-
-    before beforeFn
-    afterEach afterEachFn
-
-    it 'should integrate with a http server', (done) ->
-      httpInst = http.createServer (req, res) -> res.end()
-      enableDestroy httpInst
-      chatServer1 = new ChatService { http : httpInst }, null, state
-      httpInst.listen port
-      cleanup = (error) ->
-        chatServer1.close ->
-          httpInst.destroy done, error
-      process.once 'uncaughtException', cleanup
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', (u) ->
-        expect(u).equal(user1)
-        process.removeListener 'uncaughtException', cleanup
-        cleanup()
-
-    it 'should integrate with an existing io', (done) ->
-      io = socketIO port
-      chatServer1 = new ChatService { io : io }, null, state
-      cleanup =  (error) ->
-        chatServer1.close ->
-          chatServer1.close()
-          io.close()
-          done error
-      process.once 'uncaughtException', cleanup
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', (u) ->
-        expect(u).equal(user1)
-        process.removeListener 'uncaughtException', cleanup
-        cleanup()
-
-
-    it 'should spawn a new io server', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', (u) ->
-        expect(u).equal(user1)
-        done()
-
-    it 'should disconnect users on a server shutdown', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', ->
-        socket1.once 'disconnect', ->
-          done()
-        chatServer.close()
-        chatServer = null
-
-    it 'should reject an empty user query', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      socket1 = ioClient.connect url1
-      , { 'multiplex' : false
-        , 'reconnection' : false
-        , 'transports' : [ 'websocket' ] }
-      socket1.on 'loginRejected', ->
-        done()
-
-    it 'should restore user state from onConnect hook', (done) ->
-      userName = 'user'
-      onConnect = (server, socket, cb) ->
-        cb null, userName, { whitelistOnly : true }
-      chatServer = new ChatService { port : port }, { onConnect : onConnect }
-      , state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', (u) ->
-        expect(u).equal(userName)
-        chatServer.chatState.getOnlineUser userName, (error, u) ->
-          expect(u.username).equal(userName)
-          u.directMessagingState.whitelistOnlyGet (error, data) ->
-            expect(data).true
+    describe "State: #{state}", ->
+
+      afterEach afterEachFn
+
+
+      describe 'Initialization', ->
+
+        it 'should integrate with a http server', (done) ->
+          httpInst = http.createServer (req, res) -> res.end()
+          enableDestroy httpInst
+          chatServer1 = new ChatService { http : httpInst }, null, state
+          httpInst.listen port
+          cleanup = (error) ->
+            chatServer1.close ->
+              httpInst.destroy done, error
+          process.once 'uncaughtException', cleanup
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', (u) ->
+            expect(u).equal(user1)
+            process.removeListener 'uncaughtException', cleanup
+            cleanup()
+
+        it 'should integrate with an existing io', (done) ->
+          io = socketIO port
+          chatServer1 = new ChatService { io : io }, null, state
+          cleanup =  (error) ->
+            chatServer1.close ->
+              chatServer1.close()
+              io.close()
+              done error
+          process.once 'uncaughtException', cleanup
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', (u) ->
+            expect(u).equal(user1)
+            process.removeListener 'uncaughtException', cleanup
+            cleanup()
+
+
+        it 'should spawn a new io server', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', (u) ->
+            expect(u).equal(user1)
             done()

-    it 'should restore room state from onStart hook', (done) ->
-      room = null
-      msg1 = { author : user1, textMessage : "message", timestamp : 0 }
-      fn = ->
-        chatServer.chatState.getRoom 'roomName', (error, r) ->
-          expect(r.name).equal(room.name)
-          r.roomState.getList 'whitelist', (error, list) ->
-            expect(list).include(user1)
-            r.roomState.messagesGet (error, data) ->
-              expect(data).instanceof(Array)
-              expect(data[0]).deep.equal(msg1)
+
+      describe 'Data management', ->
+
+        it 'should restore user state from onConnect hook', (done) ->
+          userName = 'user'
+          onConnect = (server, socket, cb) ->
+            cb null, userName, { whitelistOnly : true }
+          chatServer = new ChatService { port : port }
+          , { onConnect : onConnect }
+          , state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', (u) ->
+            expect(u).equal(userName)
+            chatServer.chatState.getOnlineUser userName, (error, u) ->
+              expect(u.username).equal(userName)
+              u.directMessagingState.whitelistOnlyGet (error, data) ->
+                expect(data).true
+                done()
+
+        it 'should restore room state from onStart hook', (done) ->
+          room = null
+          msg1 = { author : user1, textMessage : "message", timestamp : 0 }
+          fn = ->
+            chatServer.chatState.getRoom 'roomName', (error, r) ->
+              expect(r.name).equal(room.name)
+              r.roomState.getList 'whitelist', (error, list) ->
+                expect(list).include(user1)
+                r.roomState.messagesGet (error, data) ->
+                  expect(data).instanceof(Array)
+                  expect(data[0]).deep.equal(msg1)
+                  done()
+          onStart = (server, cb) ->
+            room = new Room server, 'roomName'
+            room.initState { whitelist : [ user1 ]
+              , owner : user2
+              , lastMessages : [ msg1 ] }
+            , ->
+              server.chatState.addRoom room, ->
+                cb null, room
+                fn()
+          chatServer = new ChatService { port : port }
+          , { onStart : onStart }, state
+
+        it 'should support server side user disconnection', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', (u) ->
+            expect(u).equal(user1)
+            socket2 = ioClient.connect url1, makeParams(user1)
+            socket2.on 'loginConfirmed', (u) ->
+              expect(u).equal(user1)
+              chatServer.chatState.removeUser user1
+              async.parallel [ (cb) ->
+                socket1.on 'disconnect', ->
+                  expect(socket1.connected).not.ok
+                  cb()
+              , (cb) ->
+                socket2.on 'disconnect', ->
+                  expect(socket2.connected).not.ok
+                  cb()
+              ], done
+
+        it 'should disconnect only connected users', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', (u) ->
+            chatServer.chatState.removeUser user2, (error, data) ->
+              expect(error).ok
               done()
-      onStart = (server, cb) ->
-        room = new Room server, 'roomName'
-        room.initState { whitelist : [ user1 ]
-          , owner : user2
-          , lastMessages : [ msg1 ] }
-        , ->
-          server.chatState.addRoom room, ->
-            cb null, room
-            fn()
-      chatServer = new ChatService { port : port }, { onStart : onStart }, state
-
-    it 'should check existing rooms before adding new ones', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      chatServer.chatState.addRoom roomName1, ->
-        chatServer.chatState.addRoom roomName1, (error, data) ->
-          expect(error).ok
-          done()
-
-    it 'should check existing rooms before removing a room', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      chatServer.chatState.removeRoom roomName1, (error, data) ->
-        expect(error).ok
-        done()
-
-    it 'should support server side user disconnection', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', (u) ->
-        expect(u).equal(user1)
-        socket2 = ioClient.connect url1, makeParams(user1)
-        socket2.on 'loginConfirmed', (u) ->
-          expect(u).equal(user1)
-          chatServer.chatState.removeUser user1
-          async.parallel [ (cb) ->
-            socket1.on 'disconnect', ->
-              expect(socket1.connected).not.ok
-              cb()
-          , (cb) ->
-            socket2.on 'disconnect', ->
-              expect(socket2.connected).not.ok
-              cb()
-          ], done
-
-    it 'should disconnect only connected users', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', (u) ->
-        chatServer.chatState.removeUser user2, (error, data) ->
-          expect(error).ok
-          done()
-
-    it 'should support adding and removing users', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      chatServer.chatState.addUser user1, ->
-        socket1 = ioClient.connect url1, makeParams(user1)
-        socket1.on 'loginConfirmed', (u) ->
-          chatServer.chatState.getOnlineUser user1, (error, user) ->
-            expect(error).not.ok
-            user.directMessagingState.whitelistOnlyGet (error, wl) ->
-              expect(wl).true
-              chatServer.chatState.removeUser user1, ->
-                async.parallel [ (cb) ->
-                  chatServer.chatState.getUser user1, (error, user, isOnline) ->
-                    expect(user).not.ok
-                    expect(isOnline).not.ok
-                    cb()
-                , (cb) ->
-                  chatServer.chatState.getOnlineUser user1, (error, user) ->
-                    expect(error).ok
-                    cb()
-                ], done
-      , { whitelistOnly : true }
-
-    it 'should check existing users before adding new ones', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      chatServer.chatState.addUser user1, ->
-        socket1 = ioClient.connect url1, makeParams(user1)
-        socket1.on 'loginConfirmed', (u) ->
-          expect(u).equal(user1)
-          chatServer.chatState.addUser user1, (error, data) ->
+
+        it 'should support adding and removing users', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          chatServer.chatState.addUser user1, ->
+            socket1 = ioClient.connect url1, makeParams(user1)
+            socket1.on 'loginConfirmed', (u) ->
+              chatServer.chatState.getOnlineUser user1, (error, user) ->
+                expect(error).not.ok
+                user.directMessagingState.whitelistOnlyGet (error, wl) ->
+                  expect(wl).true
+                  chatServer.chatState.removeUser user1, ->
+                    async.parallel [ (cb) ->
+                      chatServer.chatState.getUser user1
+                      , (error, user, isOnline) ->
+                        expect(user).not.ok
+                        expect(isOnline).not.ok
+                        cb()
+                    , (cb) ->
+                      chatServer.chatState.getOnlineUser user1, (error, user) ->
+                        expect(error).ok
+                        cb()
+                    ], done
+          , { whitelistOnly : true }
+
+        it 'should check existing users before adding new ones', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          chatServer.chatState.addUser user1, ->
+            socket1 = ioClient.connect url1, makeParams(user1)
+            socket1.on 'loginConfirmed', (u) ->
+              expect(u).equal(user1)
+              chatServer.chatState.addUser user1, (error, data) ->
+                expect(error).ok
+                done()
+
+
+      describe 'Connection', ->
+
+        it 'should reject an empty user query', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          socket1 = ioClient.connect url1, makeParams()
+          socket1.on 'loginRejected', ->
+            done()
+
+        it 'should support multiple sockets per user', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', (u) ->
+            socket2 = ioClient.connect url1, makeParams(user1)
+            socket2.on 'loginConfirmed', (u) ->
+              chatServer.chatState.getOnlineUser user1, (error, user) ->
+                user.userState.socketsGetAll (error, sockets) ->
+                  expect(sockets).length(2)
+                  done()
+
+        it 'should disconnect all users on a server shutdown', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', ->
+            socket1.once 'disconnect', ->
+              done()
+            chatServer.close()
+            chatServer = null
+
+
+      describe 'Rooms', ->
+
+        it 'should create and delete rooms', (done) ->
+          chatServer = new ChatService { port : port
+            , enableRoomsManagement : true }
+          , null, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', (u) ->
+            socket1.emit 'roomCreate', roomName1, false, (error, data) ->
+              chatServer.chatState.listRooms (error, data) ->
+                expect(data).length(1)
+                expect(data[0]).equal(roomName1)
+                socket1.emit 'roomCreate', roomName1, false, (error, data) ->
+                  expect(error).ok
+                  socket1.emit 'roomDelete', roomName1, (error, data) ->
+                    expect(error).not.ok
+                    chatServer.chatState.listRooms (error, data) ->
+                      expect(data).empty
+                      done()
+
+        it 'should reject room management when option is disabled'
+        , (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName2
+          chatServer.chatState.addRoom room, ->
+            socket1 = ioClient.connect url1, makeParams(user1)
+            socket1.on 'loginConfirmed', (u) ->
+              socket1.emit 'roomCreate', roomName1, false, (error, data) ->
+                expect(error).ok
+                socket1.emit 'roomDelete', roomName2, (error, data) ->
+                  expect(error).ok
+                  done()
+
+        it 'should check existing rooms before adding new ones', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          chatServer.chatState.addRoom roomName1, ->
+            chatServer.chatState.addRoom roomName1, (error, data) ->
+              expect(error).ok
+              done()
+
+        it 'should check existing rooms before removing a room', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          chatServer.chatState.removeRoom roomName1, (error, data) ->
             expect(error).ok
             done()

-    it 'should support multiple sockets per user', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', (u) ->
-        socket2 = ioClient.connect url1, makeParams(user1)
-        socket2.on 'loginConfirmed', (u) ->
-          chatServer.chatState.getOnlineUser user1, (error, user) ->
-            user.userState.socketsGetAll (error, sockets) ->
-              expect(sockets).length(2)
-              done()
-
-    it 'should create and delete rooms', (done) ->
-      chatServer = new ChatService { port : port, enableRoomsManagement : true }
-      , null, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', (u) ->
-        socket1.emit 'roomCreate', roomName1, false, (error, data) ->
-          chatServer.chatState.listRooms (error, data) ->
-            expect(data).length(1)
-            expect(data[0]).equal(roomName1)
-            socket1.emit 'roomCreate', roomName1, false, (error, data) ->
-              expect(error).ok
-              socket1.emit 'roomDelete', roomName1, (error, data) ->
-                expect(error).not.ok
-                chatServer.chatState.listRooms (error, data) ->
-                  expect(data).empty
-                  done()
-
-    it 'should reject room management when option is disabled'
-    , (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName2
-      chatServer.chatState.addRoom room, ->
-        socket1 = ioClient.connect url1, makeParams(user1)
-        socket1.on 'loginConfirmed', (u) ->
-          socket1.emit 'roomCreate', roomName1, false, (error, data) ->
-            expect(error).ok
-            socket1.emit 'roomDelete', roomName2, (error, data) ->
-              expect(error).ok
-              done()
-
-    it 'should emit join and leave for user\'s sockets', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName1
-      chatServer.chatState.addRoom room, ->
-        socket1 = ioClient.connect url1, makeParams(user1)
-        socket1.on 'loginConfirmed', ->
-          socket2 = ioClient.connect url1, makeParams(user1)
-          socket2.on 'loginConfirmed', ->
-            socket2.emit 'roomJoin', roomName1
-            socket1.on 'roomJoinedEcho', (room) ->
-              expect(room).equal(roomName1)
-              socket1.emit 'roomLeave', roomName1
-              socket2.on 'roomLeftEcho', (room) ->
-                expect(room).equal(roomName1)
-                done()
-
-    it 'should broadcast join and leave room messages', (done) ->
-      chatServer = new ChatService { port : port, enableUserlistUpdates : true }
-      , null, state
-      room = new Room chatServer, roomName1
-      chatServer.chatState.addRoom room, ->
-        socket1 = ioClient.connect url1, makeParams(user1)
-        socket1.on 'loginConfirmed', ->
-          socket1.emit 'roomJoin', roomName1, (error, data) ->
-            socket2 = ioClient.connect url1, makeParams(user2)
-            socket2.on 'loginConfirmed', ->
-              socket2.emit 'roomJoin', roomName1
-              socket1.on 'roomUserJoined', (room, user) ->
-                expect(room).equal(roomName1)
-                expect(user).equal(user2)
-                socket2.emit 'roomLeave', roomName1
-                socket1.on 'roomUserLeft', (room, user) ->
+        it 'should emit join and leave for user\'s sockets', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName1
+          chatServer.chatState.addRoom room, ->
+            socket1 = ioClient.connect url1, makeParams(user1)
+            socket1.on 'loginConfirmed', ->
+              socket2 = ioClient.connect url1, makeParams(user1)
+              socket2.on 'loginConfirmed', ->
+                socket2.emit 'roomJoin', roomName1
+                socket1.on 'roomJoinedEcho', (room) ->
                   expect(room).equal(roomName1)
-                  expect(user).equal(user2)
-                  done()
-
-    it 'should store and send room history', (done) ->
-      txt = 'Test message.'
-      message = { textMessage : txt }
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName1
-      chatServer.chatState.addRoom room, ->
-        socket1 = ioClient.connect url1, makeParams(user1)
-        socket1.on 'loginConfirmed', ->
-          socket1.emit 'roomJoin', roomName1, (error, data) ->
-            socket1.emit 'roomMessage', roomName1, message, (error, data) ->
-              socket1.emit 'roomHistory', roomName1, (error, data) ->
-                expect(data?[0].textMessage).equal(txt)
-                done()
-
-    it 'should send room messages to all joined users', (done) ->
-      txt = 'Test message.'
-      message = { textMessage : txt }
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName1
-      chatServer.chatState.addRoom room, ->
-        socket1 = ioClient.connect url1, makeParams(user1)
-        socket1.on 'loginConfirmed', ->
-          socket1.emit 'roomJoin', roomName1, (error, data) ->
-            socket2 = ioClient.connect url1, makeParams(user2)
-            socket2.on 'loginConfirmed', ->
-              socket2.emit 'roomJoin', roomName1, (error, data) ->
-                socket1.emit 'roomMessage', roomName1, message
-                async.parallel [ (cb) ->
-                  socket1.on 'roomMessage', (room, user, msg) ->
+                  socket1.emit 'roomLeave', roomName1
+                  socket2.on 'roomLeftEcho', (room) ->
                     expect(room).equal(roomName1)
-                    expect(user).equal(user1)
-                    expect(msg.textMessage).equal(txt)
-                    expect(msg).ownProperty('timestamp')
-                    cb()
-                , (cb) ->
-                  socket2.on 'roomMessage', (room, user, msg) ->
+                    done()
+
+        it 'should broadcast join and leave room messages', (done) ->
+          chatServer = new ChatService { port : port
+            , enableUserlistUpdates : true }
+          , null, state
+          room = new Room chatServer, roomName1
+          chatServer.chatState.addRoom room, ->
+            socket1 = ioClient.connect url1, makeParams(user1)
+            socket1.on 'loginConfirmed', ->
+              socket1.emit 'roomJoin', roomName1, (error, data) ->
+                socket2 = ioClient.connect url1, makeParams(user2)
+                socket2.on 'loginConfirmed', ->
+                  socket2.emit 'roomJoin', roomName1
+                  socket1.on 'roomUserJoined', (room, user) ->
                     expect(room).equal(roomName1)
-                    expect(user).equal(user1)
-                    expect(msg.textMessage).equal(txt)
-                    expect(msg).ownProperty('timestamp')
-                    cb()
-                ]
-                , -> done()
-
-    it 'should reject room messages from not joined users', (done) ->
-      txt = 'Test message.'
-      message = { textMessage : txt }
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName1
-      chatServer.chatState.addRoom room, ->
-        socket1 = ioClient.connect url1, makeParams(user1)
-        socket1.on 'loginConfirmed', ->
-          socket1.emit 'roomMessage', roomName1, message, (error, data) ->
-            expect(error).ok
-            socket1.emit 'roomHistory', roomName1, (error, data) ->
-              expect(error).ok
-              done()
-
-    it 'should list all rooms', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      room1 = new Room chatServer, roomName1
-      room1.initState { whitelistOnly : true }, ->
-        room2 = new Room chatServer, roomName2
-        chatServer.chatState.addRoom room1, ->
-          chatServer.chatState.addRoom room2, ->
-            socket1 = ioClient.connect url1, makeParams(user1)
-            socket1.on 'loginConfirmed', ->
-              socket1.emit 'listRooms', (error, data) ->
-                expect(data).an('array')
-                expect(data).include(roomName2)
-                expect(data).include(roomName1)
-                done()
-
-    it 'should send whitelistonly mode', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      room1 = new Room chatServer, roomName1
-      room1.initState { whitelistOnly : true }, ->
-        chatServer.chatState.addRoom room1, ->
-          socket1 = ioClient.connect url1, makeParams(user1)
-          socket1.on 'loginConfirmed', ->
-            socket1.emit 'roomGetWhitelistMode', roomName1, (error, data) ->
-              expect(data).true
-              done()
-
-    it 'should send lists to room users', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName1
-      chatServer.chatState.addRoom room, ->
-        socket1 = ioClient.connect url1, makeParams(user1)
-        socket1.on 'loginConfirmed', ->
-          socket1.emit 'roomJoin', roomName1, ->
-            socket1.emit 'roomGetAccessList', roomName1, 'userlist'
-            , (error, data) ->
-              expect(data).an('array')
-              expect(data).include(user1)
-              done()
-
-
-    it 'should reject send lists to not joined users', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName1
-      chatServer.chatState.addRoom room, ->
-        socket1 = ioClient.connect url1, makeParams(user1)
-        socket1.on 'loginConfirmed', ->
-          socket1.emit 'roomGetAccessList', roomName1, 'userlist'
-          , (error, data) ->
-            expect(error).ok
-            done()
-
-    it 'should ckeck room list names', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName1
-      chatServer.chatState.addRoom room, ->
-        socket1 = ioClient.connect url1, makeParams(user1)
-        socket1.on 'loginConfirmed', ->
-          socket1.emit 'roomJoin', roomName1, ->
-            socket1.emit 'roomGetAccessList', roomName1, 'nolist'
-            , (error, data) ->
-              expect(error).ok
-              done()
-
-    it 'should allow list modify for admins', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName1
-      room.roomState.addToList 'adminlist', [user1], ->
-        chatServer.chatState.addRoom room, ->
-          socket1 = ioClient.connect url1, makeParams(user1)
-          socket1.on 'loginConfirmed', ->
-            socket1.emit 'roomJoin',  roomName1, (error, data) ->
-              socket1.emit 'roomAddToList', roomName1, 'whitelist', [user2]
-              , (error, data) ->
-                room.roomState.getList 'whitelist', (error, data) ->
-                  expect(data).include(user2)
-                  socket1.emit 'roomRemoveFromList', roomName1, 'whitelist'
-                  , [user2], (error, data) ->
-                    room.roomState.getList 'whitelist', (error, data) ->
-                      expect(data).not.include(user2)
+                    expect(user).equal(user2)
+                    socket2.emit 'roomLeave', roomName1
+                    socket1.on 'roomUserLeft', (room, user) ->
+                      expect(room).equal(roomName1)
+                      expect(user).equal(user2)
                       done()

-    it 'should reject userlist modifications', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName1
-      room.roomState.addToList 'adminlist', [user1], ->
-        chatServer.chatState.addRoom room, ->
-          socket1 = ioClient.connect url1, makeParams(user1)
-          socket1.on 'loginConfirmed', ->
-            socket1.emit 'roomJoin',  roomName1, (error, data) ->
-              socket1.emit 'roomAddToList', roomName1, 'userlist', [user2]
+        it 'should store and send room history', (done) ->
+          txt = 'Test message.'
+          message = { textMessage : txt }
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName1
+          chatServer.chatState.addRoom room, ->
+            socket1 = ioClient.connect url1, makeParams(user1)
+            socket1.on 'loginConfirmed', ->
+              socket1.emit 'roomJoin', roomName1, (error, data) ->
+                socket1.emit 'roomMessage', roomName1, message, (error, data) ->
+                  socket1.emit 'roomHistory', roomName1, (error, data) ->
+                    expect(data?[0].textMessage).equal(txt)
+                    done()
+
+        it 'should send room messages to all joined users', (done) ->
+          txt = 'Test message.'
+          message = { textMessage : txt }
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName1
+          chatServer.chatState.addRoom room, ->
+            socket1 = ioClient.connect url1, makeParams(user1)
+            socket1.on 'loginConfirmed', ->
+              socket1.emit 'roomJoin', roomName1, (error, data) ->
+                socket2 = ioClient.connect url1, makeParams(user2)
+                socket2.on 'loginConfirmed', ->
+                  socket2.emit 'roomJoin', roomName1, (error, data) ->
+                    socket1.emit 'roomMessage', roomName1, message
+                    async.parallel [ (cb) ->
+                      socket1.on 'roomMessage', (room, user, msg) ->
+                        expect(room).equal(roomName1)
+                        expect(user).equal(user1)
+                        expect(msg.textMessage).equal(txt)
+                        expect(msg).ownProperty('timestamp')
+                        cb()
+                    , (cb) ->
+                      socket2.on 'roomMessage', (room, user, msg) ->
+                        expect(room).equal(roomName1)
+                        expect(user).equal(user1)
+                        expect(msg.textMessage).equal(txt)
+                        expect(msg).ownProperty('timestamp')
+                        cb()
+                    ]
+                    , -> done()
+
+        it 'should reject room messages from not joined users', (done) ->
+          txt = 'Test message.'
+          message = { textMessage : txt }
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName1
+          chatServer.chatState.addRoom room, ->
+            socket1 = ioClient.connect url1, makeParams(user1)
+            socket1.on 'loginConfirmed', ->
+              socket1.emit 'roomMessage', roomName1, message, (error, data) ->
+                expect(error).ok
+                socket1.emit 'roomHistory', roomName1, (error, data) ->
+                  expect(error).ok
+                  done()
+
+        it 'should list all rooms', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          room1 = new Room chatServer, roomName1
+          room1.initState { whitelistOnly : true }, ->
+            room2 = new Room chatServer, roomName2
+            chatServer.chatState.addRoom room1, ->
+              chatServer.chatState.addRoom room2, ->
+                socket1 = ioClient.connect url1, makeParams(user1)
+                socket1.on 'loginConfirmed', ->
+                  socket1.emit 'listRooms', (error, data) ->
+                    expect(data).an('array')
+                    expect(data).include(roomName2)
+                    expect(data).include(roomName1)
+                    done()
+
+        it 'should send whitelistonly mode', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          room1 = new Room chatServer, roomName1
+          room1.initState { whitelistOnly : true }, ->
+            chatServer.chatState.addRoom room1, ->
+              socket1 = ioClient.connect url1, makeParams(user1)
+              socket1.on 'loginConfirmed', ->
+                socket1.emit 'roomGetWhitelistMode', roomName1, (error, data) ->
+                  expect(data).true
+                  done()
+
+        it 'should send lists to room users', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName1
+          chatServer.chatState.addRoom room, ->
+            socket1 = ioClient.connect url1, makeParams(user1)
+            socket1.on 'loginConfirmed', ->
+              socket1.emit 'roomJoin', roomName1, ->
+                socket1.emit 'roomGetAccessList', roomName1, 'userlist'
+                , (error, data) ->
+                  expect(data).an('array')
+                  expect(data).include(user1)
+                  done()
+
+
+        it 'should reject send lists to not joined users', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName1
+          chatServer.chatState.addRoom room, ->
+            socket1 = ioClient.connect url1, makeParams(user1)
+            socket1.on 'loginConfirmed', ->
+              socket1.emit 'roomGetAccessList', roomName1, 'userlist'
               , (error, data) ->
                 expect(error).ok
                 done()

-    it 'should reject admin list modifications for admins', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName1
-      room.roomState.addToList 'adminlist', [user1,user2], ->
-        chatServer.chatState.addRoom room, ->
-          socket1 = ioClient.connect url1, makeParams(user1)
-          socket1.on 'loginConfirmed', ->
-            socket1.emit 'roomJoin',  roomName1, (error, data) ->
-              socket2 = ioClient.connect url1, makeParams(user2)
-              socket2.on 'loginConfirmed', ->
-                socket2.emit 'roomJoin',  roomName1, (error, data) ->
-                  socket1.emit 'roomRemoveFromList', roomName1, 'adminlist'
-                  , [user2] , (error, data) ->
+        it 'should ckeck room list names', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName1
+          chatServer.chatState.addRoom room, ->
+            socket1 = ioClient.connect url1, makeParams(user1)
+            socket1.on 'loginConfirmed', ->
+              socket1.emit 'roomJoin', roomName1, ->
+                socket1.emit 'roomGetAccessList', roomName1, 'nolist'
+                , (error, data) ->
+                  expect(error).ok
+                  done()
+
+        it 'should allow list modify for admins', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName1
+          room.roomState.addToList 'adminlist', [user1], ->
+            chatServer.chatState.addRoom room, ->
+              socket1 = ioClient.connect url1, makeParams(user1)
+              socket1.on 'loginConfirmed', ->
+                socket1.emit 'roomJoin',  roomName1, (error, data) ->
+                  socket1.emit 'roomAddToList', roomName1, 'whitelist', [user2]
+                  , (error, data) ->
+                    room.roomState.getList 'whitelist', (error, data) ->
+                      expect(data).include(user2)
+                      socket1.emit 'roomRemoveFromList', roomName1, 'whitelist'
+                      , [user2], (error, data) ->
+                        room.roomState.getList 'whitelist', (error, data) ->
+                          expect(data).not.include(user2)
+                          done()
+
+        it 'should reject userlist modifications', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName1
+          room.roomState.addToList 'adminlist', [user1], ->
+            chatServer.chatState.addRoom room, ->
+              socket1 = ioClient.connect url1, makeParams(user1)
+              socket1.on 'loginConfirmed', ->
+                socket1.emit 'roomJoin',  roomName1, (error, data) ->
+                  socket1.emit 'roomAddToList', roomName1, 'userlist', [user2]
+                  , (error, data) ->
                     expect(error).ok
                     done()

-    it 'should reject lists modify for users', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName1
-      chatServer.chatState.addRoom room, ->
-        socket1 = ioClient.connect url1, makeParams(user1)
-        socket1.on 'loginConfirmed', ->
-          socket1.emit 'roomJoin', roomName1, (error, data) ->
-            socket1.emit 'roomAddToList', roomName1, 'adminlist', [user2]
-            , (error, data) ->
-              expect(error).ok
-              done()
-
-    it 'should check room permissions', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName1
-      room.roomState.addToList 'blacklist', [user1], ->
-        chatServer.chatState.addRoom room, ->
-          socket1 = ioClient.connect url1, makeParams(user1)
-          socket1.on 'loginConfirmed', ->
-            socket1.emit 'roomJoin', roomName1, (error, data) ->
-              expect(error).ok
-              done()
-
-    it 'should check room permissions in whitelist mode', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName1
-      room.roomState.whitelistOnlySet true, ->
-        room.roomState.addToList 'whitelist', [user2], ->
+        it 'should reject admin list modifications for admins', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName1
+          room.roomState.addToList 'adminlist', [user1,user2], ->
+            chatServer.chatState.addRoom room, ->
+              socket1 = ioClient.connect url1, makeParams(user1)
+              socket1.on 'loginConfirmed', ->
+                socket1.emit 'roomJoin',  roomName1, (error, data) ->
+                  socket2 = ioClient.connect url1, makeParams(user2)
+                  socket2.on 'loginConfirmed', ->
+                    socket2.emit 'roomJoin',  roomName1, (error, data) ->
+                      socket1.emit 'roomRemoveFromList', roomName1, 'adminlist'
+                      , [user2] , (error, data) ->
+                        expect(error).ok
+                        done()
+
+        it 'should reject lists modify for users', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName1
           chatServer.chatState.addRoom room, ->
             socket1 = ioClient.connect url1, makeParams(user1)
             socket1.on 'loginConfirmed', ->
               socket1.emit 'roomJoin', roomName1, (error, data) ->
-                expect(error).ok
-                socket2 = ioClient.connect url1, makeParams(user2)
-                socket2.on 'loginConfirmed', ->
-                  socket2.emit 'roomJoin', roomName1, (error, data) ->
-                    done()
-
-    it 'should remove users on permission changes', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName1
-      room.roomState.addToList 'adminlist', [user1], ->
-        chatServer.chatState.addRoom room, ->
-          socket1 = ioClient.connect url1, makeParams(user1)
-          socket1.on 'loginConfirmed', ->
-            socket1.emit 'roomJoin', roomName1, (error, data) ->
-              socket2 = ioClient.connect url1, makeParams(user2)
-              socket2.on 'loginConfirmed', ->
-                socket2.emit 'roomJoin', roomName1, (error, data) ->
-                  socket1.emit 'roomAddToList', roomName1, 'blacklist', [user2]
-                  socket2.on 'roomAccessRemoved', (r) ->
-                    expect(r).equal(roomName1)
-                    done()
-
-    it 'should remove users on mode changes', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName1
-      room.roomState.addToList 'adminlist', [user1], ->
-        chatServer.chatState.addRoom room, ->
-          socket1 = ioClient.connect url1, makeParams(user1)
-          socket1.on 'loginConfirmed', ->
-            socket1.emit 'roomJoin', roomName1, (error, data) ->
-              socket2 = ioClient.connect url1, makeParams(user2)
-              socket2.on 'loginConfirmed', ->
-                socket2.emit 'roomJoin', roomName1, (error, data) ->
-                  socket1.emit 'roomSetWhitelistMode', roomName1, true
-                  socket2.on 'roomAccessRemoved', (r) ->
-                    expect(r).equal(roomName1)
-                    done()
-
-    it 'should remove users on permission changes in whitelist mode', (done) ->
-      chatServer = new ChatService { port : port }, null, state
-      room = new Room chatServer, roomName1
-      room.roomState.addToList 'adminlist', [user1], ->
-        room.roomState.addToList 'whitelist', [user2], ->
+                socket1.emit 'roomAddToList', roomName1, 'adminlist', [user2]
+                , (error, data) ->
+                  expect(error).ok
+                  done()
+
+        it 'should check room permissions', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName1
+          room.roomState.addToList 'blacklist', [user1], ->
+            chatServer.chatState.addRoom room, ->
+              socket1 = ioClient.connect url1, makeParams(user1)
+              socket1.on 'loginConfirmed', ->
+                socket1.emit 'roomJoin', roomName1, (error, data) ->
+                  expect(error).ok
+                  done()
+
+        it 'should check room permissions in whitelist mode', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName1
           room.roomState.whitelistOnlySet true, ->
+            room.roomState.addToList 'whitelist', [user2], ->
+              chatServer.chatState.addRoom room, ->
+                socket1 = ioClient.connect url1, makeParams(user1)
+                socket1.on 'loginConfirmed', ->
+                  socket1.emit 'roomJoin', roomName1, (error, data) ->
+                    expect(error).ok
+                    socket2 = ioClient.connect url1, makeParams(user2)
+                    socket2.on 'loginConfirmed', ->
+                      socket2.emit 'roomJoin', roomName1, (error, data) ->
+                        done()
+
+        it 'should remove users on permission changes', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName1
+          room.roomState.addToList 'adminlist', [user1], ->
             chatServer.chatState.addRoom room, ->
               socket1 = ioClient.connect url1, makeParams(user1)
               socket1.on 'loginConfirmed', ->
@@ -558,248 +541,300 @@
                   socket2 = ioClient.connect url1, makeParams(user2)
                   socket2.on 'loginConfirmed', ->
                     socket2.emit 'roomJoin', roomName1, (error, data) ->
-                      socket1.emit 'roomRemoveFromList', roomName1
-                      , 'whitelist', [user2]
+                      socket1.emit 'roomAddToList', roomName1, 'blacklist'
+                      , [user2]
                       socket2.on 'roomAccessRemoved', (r) ->
                         expect(r).equal(roomName1)
                         done()

-    it 'should send access remove on room delete', (done) ->
-      chatServer = new ChatService { port : port, enableRoomsManagement : true }
-      , null, state
-      room = new Room chatServer, roomName1
-      room.roomState.ownerSet user1, ->
-        chatServer.chatState.addRoom room, ->
-          socket1 = ioClient.connect url1, makeParams(user1)
-          socket1.on 'loginConfirmed', ->
-            socket1.emit 'roomJoin', roomName1, (error, data) ->
-              socket1.emit 'roomDelete', roomName1
-              socket1.once 'roomAccessRemoved', (r) ->
-                expect(r).equal(roomName1)
-                done()
-
-    it 'should remove disconnected users' , (done) ->
-      chatServer = new ChatService { port : port, enableUserlistUpdates : true }
-      , null, state
-      room = new Room chatServer, roomName1
-      chatServer.chatState.addRoom room, ->
-        socket1 = ioClient.connect url1, makeParams(user1)
-        socket1.on 'loginConfirmed', ->
-          socket1.emit 'roomJoin', roomName1, (error, data) ->
+        it 'should remove users on mode changes', (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName1
+          room.roomState.addToList 'adminlist', [user1], ->
+            chatServer.chatState.addRoom room, ->
+              socket1 = ioClient.connect url1, makeParams(user1)
+              socket1.on 'loginConfirmed', ->
+                socket1.emit 'roomJoin', roomName1, (error, data) ->
+                  socket2 = ioClient.connect url1, makeParams(user2)
+                  socket2.on 'loginConfirmed', ->
+                    socket2.emit 'roomJoin', roomName1, (error, data) ->
+                      socket1.emit 'roomSetWhitelistMode', roomName1, true
+                      socket2.on 'roomAccessRemoved', (r) ->
+                        expect(r).equal(roomName1)
+                        done()
+
+        it 'should remove users on permission changes in whitelist mode'
+        , (done) ->
+          chatServer = new ChatService { port : port }, null, state
+          room = new Room chatServer, roomName1
+          room.roomState.addToList 'adminlist', [user1], ->
+            room.roomState.addToList 'whitelist', [user2], ->
+              room.roomState.whitelistOnlySet true, ->
+                chatServer.chatState.addRoom room, ->
+                  socket1 = ioClient.connect url1, makeParams(user1)
+                  socket1.on 'loginConfirmed', ->
+                    socket1.emit 'roomJoin', roomName1, (error, data) ->
+                      socket2 = ioClient.connect url1, makeParams(user2)
+                      socket2.on 'loginConfirmed', ->
+                        socket2.emit 'roomJoin', roomName1, (error, data) ->
+                          socket1.emit 'roomRemoveFromList', roomName1
+                          , 'whitelist', [user2]
+                          socket2.on 'roomAccessRemoved', (r) ->
+                            expect(r).equal(roomName1)
+                            done()
+
+        it 'should send access remove on room delete', (done) ->
+          chatServer = new ChatService { port : port
+            , enableRoomsManagement : true }
+          , null, state
+          room = new Room chatServer, roomName1
+          room.roomState.ownerSet user1, ->
+            chatServer.chatState.addRoom room, ->
+              socket1 = ioClient.connect url1, makeParams(user1)
+              socket1.on 'loginConfirmed', ->
+                socket1.emit 'roomJoin', roomName1, (error, data) ->
+                  socket1.emit 'roomDelete', roomName1
+                  socket1.once 'roomAccessRemoved', (r) ->
+                    expect(r).equal(roomName1)
+                    done()
+
+        it 'should remove disconnected users' , (done) ->
+          chatServer = new ChatService { port : port
+            , enableUserlistUpdates : true }
+          , null, state
+          room = new Room chatServer, roomName1
+          chatServer.chatState.addRoom room, ->
+            socket1 = ioClient.connect url1, makeParams(user1)
+            socket1.on 'loginConfirmed', ->
+              socket1.emit 'roomJoin', roomName1, (error, data) ->
+                socket2 = ioClient.connect url1, makeParams(user2)
+                socket2.on 'loginConfirmed', ->
+                  socket2.emit 'roomJoin', roomName1, (error, data) ->
+                    socket2.disconnect()
+                    socket1.on 'roomUserLeft', (r,u) ->
+                      expect(r).equal(roomName1)
+                      expect(u).equal(user2)
+                      done()
+
+      describe 'Direct', ->
+
+        it 'should send direct messages', (done) ->
+          txt = 'Test message.'
+          message = { textMessage : txt }
+          chatServer = new ChatService { port : port
+            , enableDirectMessages : true }
+          , null, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', ->
             socket2 = ioClient.connect url1, makeParams(user2)
             socket2.on 'loginConfirmed', ->
-              socket2.emit 'roomJoin', roomName1, (error, data) ->
-                socket2.disconnect()
-                socket1.on 'roomUserLeft', (r,u) ->
-                  expect(r).equal(roomName1)
-                  expect(u).equal(user2)
-                  done()
-
-    it 'should send direct messages', (done) ->
-      txt = 'Test message.'
-      message = { textMessage : txt }
-      chatServer = new ChatService { port : port, enableDirectMessages : true }
-      , null, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', ->
-        socket2 = ioClient.connect url1, makeParams(user2)
-        socket2.on 'loginConfirmed', ->
-          socket1.emit 'directMessage', user2, message
-          socket2.on 'directMessage', (u, msg) ->
-            expect(u).equal(user1)
-            expect(msg?.textMessage).equal(txt)
-            expect(msg).ownProperty('timestamp')
-            done()
-
-    it 'should not send direct messages when option is disabled', (done) ->
-      txt = 'Test message.'
-      message = { textMessage : txt }
-      chatServer = new ChatService { port : port }
-      , null, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', ->
-        socket2 = ioClient.connect url1, makeParams(user2)
-        socket2.on 'loginConfirmed', ->
-          socket1.emit 'directMessage', user2, message, (error, data) ->
-            expect(error).ok
-            done()
-
-    it 'should echo direct messges to user\'s sockets', (done) ->
-      txt = 'Test message.'
-      message = { textMessage : txt }
-      chatServer = new ChatService { port : port, enableDirectMessages : true }
-      , null, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', ->
-        socket3 = ioClient.connect url1, makeParams(user1)
-        socket3.on 'loginConfirmed', ->
-          socket2 = ioClient.connect url1, makeParams(user2)
-          socket2.on 'loginConfirmed', ->
-            socket1.emit 'directMessage', user2, message
-            socket3.on 'directMessageEcho', (u, msg) ->
-              expect(u).equal(user2)
-              expect(msg?.textMessage).equal(txt)
-              done()
-
-    it 'should check user permission', (done) ->
-      txt = 'Test message.'
-      message = { textMessage : txt }
-      chatServer = new ChatService { port : port, enableDirectMessages : true }
-      , null, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', ->
-        socket2 = ioClient.connect url1, makeParams(user2)
-        socket2.on 'loginConfirmed', ->
-          chatServer.chatState.getOnlineUser user2, (error, user) ->
-            user.directMessagingState.addToList 'blacklist', [user1], ->
               socket1.emit 'directMessage', user2, message
-              , (error, data) ->
+              socket2.on 'directMessage', (u, msg) ->
+                expect(u).equal(user1)
+                expect(msg?.textMessage).equal(txt)
+                expect(msg).ownProperty('timestamp')
+                done()
+
+        it 'should not send direct messages when option is disabled', (done) ->
+          txt = 'Test message.'
+          message = { textMessage : txt }
+          chatServer = new ChatService { port : port }
+          , null, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', ->
+            socket2 = ioClient.connect url1, makeParams(user2)
+            socket2.on 'loginConfirmed', ->
+              socket1.emit 'directMessage', user2, message, (error, data) ->
                 expect(error).ok
                 done()

-    it 'should check user permission in whitelist mode', (done) ->
-      txt = 'Test message.'
-      message = { textMessage : txt }
-      chatServer = new ChatService { port : port, enableDirectMessages : true }
-      , null, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', ->
-        socket2 = ioClient.connect url1, makeParams(user2)
-        socket2.on 'loginConfirmed', ->
-          chatServer.chatState.getOnlineUser user2,  (error, user) ->
-            user.directMessagingState.whitelistOnlySet true, ->
-              user.directMessagingState.addToList 'whitelist', [user1], ->
+        it 'should echo direct messges to user\'s sockets', (done) ->
+          txt = 'Test message.'
+          message = { textMessage : txt }
+          chatServer = new ChatService { port : port
+            , enableDirectMessages : true }
+          , null, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', ->
+            socket3 = ioClient.connect url1, makeParams(user1)
+            socket3.on 'loginConfirmed', ->
+              socket2 = ioClient.connect url1, makeParams(user2)
+              socket2.on 'loginConfirmed', ->
                 socket1.emit 'directMessage', user2, message
-                , (error, data) ->
-                  expect(error).not.ok
-                  done()
-
-    it 'should allow user to modify own lists', (done) ->
-      chatServer = new ChatService { port : port, enableDirectMessages : true }
-      , null, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', ->
-        socket1.emit 'directAddToList', 'blacklist', [user2]
-        , (error, data) ->
-          expect(error).not.ok
-          socket1.emit 'directGetAccessList', 'blacklist'
-          , (error, data) ->
-            expect(data).include(user2)
-            socket1.emit 'directRemoveFromList', 'blacklist', [user2]
+                socket3.on 'directMessageEcho', (u, msg) ->
+                  expect(u).equal(user2)
+                  expect(msg?.textMessage).equal(txt)
+                  done()
+
+        it 'should check user permission', (done) ->
+          txt = 'Test message.'
+          message = { textMessage : txt }
+          chatServer = new ChatService { port : port
+            , enableDirectMessages : true }
+          , null, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', ->
+            socket2 = ioClient.connect url1, makeParams(user2)
+            socket2.on 'loginConfirmed', ->
+              chatServer.chatState.getOnlineUser user2, (error, user) ->
+                user.directMessagingState.addToList 'blacklist', [user1], ->
+                  socket1.emit 'directMessage', user2, message
+                  , (error, data) ->
+                    expect(error).ok
+                    done()
+
+        it 'should check user permission in whitelist mode', (done) ->
+          txt = 'Test message.'
+          message = { textMessage : txt }
+          chatServer = new ChatService { port : port
+            , enableDirectMessages : true }
+          , null, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', ->
+            socket2 = ioClient.connect url1, makeParams(user2)
+            socket2.on 'loginConfirmed', ->
+              chatServer.chatState.getOnlineUser user2,  (error, user) ->
+                user.directMessagingState.whitelistOnlySet true, ->
+                  user.directMessagingState.addToList 'whitelist', [user1], ->
+                    socket1.emit 'directMessage', user2, message
+                    , (error, data) ->
+                      expect(error).not.ok
+                      done()
+
+        it 'should allow user to modify own lists', (done) ->
+          chatServer = new ChatService { port : port
+            , enableDirectMessages : true }
+          , null, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', ->
+            socket1.emit 'directAddToList', 'blacklist', [user2]
             , (error, data) ->
               expect(error).not.ok
               socket1.emit 'directGetAccessList', 'blacklist'
               , (error, data) ->
-                expect(data).not.include(user2)
+                expect(data).include(user2)
+                socket1.emit 'directRemoveFromList', 'blacklist', [user2]
+                , (error, data) ->
+                  expect(error).not.ok
+                  socket1.emit 'directGetAccessList', 'blacklist'
+                  , (error, data) ->
+                    expect(data).not.include(user2)
+                    done()
+
+        it 'should check user list names', (done) ->
+          chatServer = new ChatService { port : port
+            , enableDirectMessages : true }
+          , null, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', ->
+            socket1.emit 'directAddToList', 'nolist', [user2]
+            , (error, data) ->
+              expect(error).ok
+              done()
+
+        it 'should allow user to modify own mode', (done) ->
+          chatServer = new ChatService { port : port
+            , enableDirectMessages : true }
+          , null, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', ->
+            socket1.emit 'directSetWhitelistMode', true, (error, data) ->
+              expect(error).not.ok
+              socket1.emit 'directGetWhitelistMode', (error, data) ->
+                expect(data).true
                 done()

-    it 'should check user list names', (done) ->
-      chatServer = new ChatService { port : port, enableDirectMessages : true }
-      , null, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', ->
-        socket1.emit 'directAddToList', 'nolist', [user2]
-        , (error, data) ->
-          expect(error).ok
-          done()
-
-    it 'should allow user to modify own mode', (done) ->
-      chatServer = new ChatService { port : port, enableDirectMessages : true }
-      , null, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', ->
-        socket1.emit 'directSetWhitelistMode', true, (error, data) ->
-          expect(error).not.ok
-          socket1.emit 'directGetWhitelistMode', (error, data) ->
-            expect(data).true
-            done()
-
-    it 'should execute before and after messages hooks', (done) ->
-      someData = 'data'
-      before = null
-      after = null
-      beforeHook = (user, name, mode, cb) ->
-        expect(user).instanceof(User)
-        expect(name).a('string')
-        expect(mode).a('boolean')
-        expect(cb).instanceof(Function)
-        before = true
-        cb()
-      afterHook = (user, error, data, args, cb) ->
-        expect(user).instanceof(User)
-        expect(args).instanceof(Array)
-        expect(cb).instanceof(Function)
-        after = true
-        cb null, someData
-      chatServer = new ChatService { port : port, enableRoomsManagement : true}
-      , { 'roomCreateBefore' : beforeHook, 'roomCreateAfter' : afterHook }
-      , state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', ->
-        socket1.emit 'roomCreate', roomName1, true, (error, data) ->
-          expect(before).true
-          expect(after).true
-          expect(error).not.ok
-          expect(data).equal(someData)
-          done()
-
-    it 'should stop commands on before hook error or data', (done) ->
-      err = 'error'
-      beforeHook = (user, cb) ->
-        cb err
-      chatServer = new ChatService { port : port }
-      , { 'listRoomsBefore' : beforeHook }, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', ->
-        socket1.emit 'listRooms', (error, data) ->
-          expect(error).equal(err)
-          done()
-
-    it 'should allow new arguments from before hook', (done) ->
-      data = 'data'
-      beforeHook = (user, cb) ->
-        cb null, null, data
-      afterHook = (user, d, cb) ->
-        expect(d).equal(data)
-        cb()
-      chatServer = new ChatService { port : port }, null, state
-      , { 'listRoomsBefore' : beforeHook, 'listRoomsAfter' : afterHook }
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', ->
-        socket1.emit 'listRooms', (error, data) ->
-          done()
-
-    it 'should execute server errors hook', (done) ->
-      error = 'some error'
-      fn = (e) ->
-        expect(e).equal(error)
-        process.nextTick -> done()
-      chatServer = new ChatService { port : port }
-      , { serverErrorHook : fn }, state
-      chatServer.errorBuilder.handleServerError error
-
-    it 'should return raw error objects', (done) ->
-      chatServer = new ChatService { port : port, useRawErrorObjects : true },
-      null, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', ->
-        socket1.emit 'roomGetAccessList', roomName1, 'nolist', (error) ->
-          expect(error.name).equal('noRoom')
-          expect(error.args).length.above(0)
-          expect(error.args[0]).equal('room1')
-          done()
-
-    it 'should validate message arguments', (done) ->
-      chatServer = new ChatService {port : port, enableRoomsManagement : true},
-      null, state
-      socket1 = ioClient.connect url1, makeParams(user1)
-      socket1.on 'loginConfirmed', (u) ->
-        socket1.emit 'roomCreate', null, false, (error, data) ->
-          expect(error).ok
-          done()
-
-    it 'should have server messages field', (done) ->
-      chatServer = new ChatService {port : port}, null, state
-      for k, fn of chatServer.serverMessages
-        fn()
-      process.nextTick -> done()
+      describe 'Other', ->
+
+        it 'should execute before and after messages hooks', (done) ->
+          someData = 'data'
+          before = null
+          after = null
+          beforeHook = (user, name, mode, cb) ->
+            expect(user).instanceof(User)
+            expect(name).a('string')
+            expect(mode).a('boolean')
+            expect(cb).instanceof(Function)
+            before = true
+            cb()
+          afterHook = (user, error, data, args, cb) ->
+            expect(user).instanceof(User)
+            expect(args).instanceof(Array)
+            expect(cb).instanceof(Function)
+            after = true
+            cb null, someData
+          chatServer = new ChatService { port : port
+            , enableRoomsManagement : true}
+          , { 'roomCreateBefore' : beforeHook, 'roomCreateAfter' : afterHook }
+          , state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', ->
+            socket1.emit 'roomCreate', roomName1, true, (error, data) ->
+              expect(before).true
+              expect(after).true
+              expect(error).not.ok
+              expect(data).equal(someData)
+              done()
+
+        it 'should stop commands on before hook error or data', (done) ->
+          err = 'error'
+          beforeHook = (user, cb) ->
+            cb err
+          chatServer = new ChatService { port : port }
+          , { 'listRoomsBefore' : beforeHook }, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', ->
+            socket1.emit 'listRooms', (error, data) ->
+              expect(error).equal(err)
+              done()
+
+        it 'should allow new arguments from before hook', (done) ->
+          data = 'data'
+          beforeHook = (user, cb) ->
+            cb null, null, data
+          afterHook = (user, d, cb) ->
+            expect(d).equal(data)
+            cb()
+          chatServer = new ChatService { port : port }, null, state
+          , { 'listRoomsBefore' : beforeHook, 'listRoomsAfter' : afterHook }
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', ->
+            socket1.emit 'listRooms', (error, data) ->
+              done()
+
+        it 'should execute server errors hook', (done) ->
+          error = 'some error'
+          fn = (e) ->
+            expect(e).equal(error)
+            process.nextTick -> done()
+          chatServer = new ChatService { port : port }
+          , { serverErrorHook : fn }, state
+          chatServer.errorBuilder.handleServerError error
+
+        it 'should return raw error objects', (done) ->
+          chatServer = new ChatService { port : port
+          , useRawErrorObjects : true },
+          null, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', ->
+            socket1.emit 'roomGetAccessList', roomName1, 'nolist', (error) ->
+              expect(error.name).equal('noRoom')
+              expect(error.args).length.above(0)
+              expect(error.args[0]).equal('room1')
+              done()
+
+        it 'should validate message arguments', (done) ->
+          chatServer = new ChatService {port : port
+            , enableRoomsManagement : true},
+          null, state
+          socket1 = ioClient.connect url1, makeParams(user1)
+          socket1.on 'loginConfirmed', (u) ->
+            socket1.emit 'roomCreate', null, false, (error, data) ->
+              expect(error).ok
+              done()
+
+        it 'should have server messages field', (done) ->
+          chatServer = new ChatService {port : port}, null, state
+          for k, fn of chatServer.serverMessages
+            fn()
+          process.nextTick -> done()


Do not require modernizr.

Do not require modernizr.

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -18,7 +18,6 @@
 #= require turbolinks
 #= require jquery.turbolinks
 #= require bootstrap
-#= require modernizr
 #= require select2
 #= require raphael
 #= require g.raphael-min


Use plurals only when needed

Use plurals only when needed

--- lib/deprecation-cop-view.coffee
+++ lib/deprecation-cop-view.coffee
@@ -1,5 +1,6 @@
 {$, $$, ScrollView} = require 'atom'
 path = require 'path'
+_ = require 'underscore-plus'
 fs = require 'fs-plus'
 Grim = require 'grim'

@@ -70,6 +71,7 @@
     deprecations.sort (a, b) -> b.getCallCount() - a.getCallCount()
     @list.empty()

+    # I feel guilty about this nested code catastrophe
     if deprecations.length == 0
       @list.append $$ ->
         @li class: 'list-item', "No deprecated calls"
@@ -80,7 +82,7 @@
           @li class: 'deprecation list-nested-item collapsed', =>
             @div class: 'deprecation-info list-item', =>
               @span class: 'text-highlight', deprecation.getOriginName()
-              @span " (called #{deprecation.getCallCount()} times)"
+              @span " (called #{_.pluralize(deprecation.getCallCount(), 'time')})"

             @ul class: 'list', =>
               @li class: 'list-item', =>
@@ -93,10 +95,10 @@
                   @div class: 'btn-toolbar', =>
                     @span class: 'icon icon-alert'
                     if packageName = self.getPackageName(stack)
-                      @span packageName + " package (called #{stack.callCount} times)"
+                      @span packageName + " package (called #{_.pluralize(stack.callCount, 'time')})"
                       @a href:self.createIssueUrl(packageName, deprecation, stack), "Create Issue on #{packageName} repo"
                     else
-                      @span "atom core"  + " (called #{stack.callCount} times)"
+                      @span "atom core"  + " (called #{_.pluralize(stack.callCount, 'time')})"

                   @div class: 'stack-trace', =>
                     for {functionName, location, fileName} in stack


Fix javascript map turbolinks integration

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -15,7 +15,7 @@
   jQuery.timeago.settings.lang = 'de';
   jQuery.timeago.settings.allowFuture = true;

-  $(document).bind 'page:change', ->
+  $(document).bind 'turbolinks:load', ->
     tileLayer = L.tileLayer('//openmensa.org/tiles/{z}/{x}/{y}.png',
       attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA',
       maxZoom: 18)


reinstated fancybox

reinstated fancybox

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -15,6 +15,7 @@
       $(this).find('.highlight').removeClass('highlight').addClass('incorrect')
     false

+  $('.fancybox').fancybox()

   $('#view_map').each ->
     lat = $(this).data('lat')


#35 Triggerin multiple event error fixed

--- app/assets/javascripts/application.js.coffee
+++ app/assets/javascripts/application.js.coffee
@@ -11,6 +11,7 @@
 # GO AFTER THE REQUIRES BELOW.
 #
 #= require jquery
+
 #= require jquery_ujs
 #= require jquery.ui.autocomplete
 #= require jquery.ui.sortable


Add a missing level link to BrainPOP's Ogre Encounter series.

--- app/views/play/CampaignView.coffee
+++ app/views/play/CampaignView.coffee
@@ -523,7 +523,7 @@
         return level.requiresSubscription
     if features.brainPop
       context.levels = _.filter context.levels, (level) ->
-        level.slug in ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'true-names']
+        level.slug in ['dungeons-of-kithgard', 'gems-in-the-deep', 'shadow-guard', 'enemy-mine', 'true-names']
     @annotateLevels(context.levels)
     count = @countLevels context.levels
     if @courseStats?


Have campaign view unlocking consider classroom language

Do not have JavaScript gameplay affect Python unlocking and
vice versa.

--- app/views/play/CampaignView.coffee
+++ app/views/play/CampaignView.coffee
@@ -53,7 +53,7 @@

   constructor: (model) ->
     super()
-    @url = "/db/user/#{me.id}/level.sessions?project=state.complete,levelID,state.difficulty,playtime,state.topScores"
+    @url = "/db/user/#{me.id}/level.sessions?project=state.complete,levelID,state.difficulty,playtime,state.topScores,codeLanguage,level"

 class CampaignsCollection extends CocoCollection
   # We don't send all of levels, just the parts needed in countLevels
@@ -337,6 +337,21 @@
     levelPlayCountsRequest.load()

   onLoaded: ->
+    classroomLevels = @classroom.getLevels()
+    classroomLevelMap = _.zipObject(classroomLevels.map((l) -> l.get('original')), classroomLevels.models)
+    defaultLanguage = @classroom.get('aceConfig').language
+    if @classroom
+      for session in @sessions.slice()
+        classroomLevel = classroomLevelMap[session.get('level').original]
+        expectedLanguage = classroomLevel.get('primerLanguage') or defaultLanguage
+        if session.get('codeLanguage') isnt expectedLanguage
+          @sessions.remove(session)
+          continue
+    for session in @sessions.models
+      unless @levelStatusMap[session.get('levelID')] is 'complete'  # Don't overwrite a complete session with an incomplete one
+        @levelStatusMap[session.get('levelID')] = if session.get('state')?.complete then 'complete' else 'started'
+      @levelDifficultyMap[session.get('levelID')] = session.get('state').difficulty if session.get('state')?.difficulty
+
     @buildLevelScoreMap() unless @editorMode
     # HoC: Fake us up a "mode" for HeroVictoryModal to return hero without levels realizing they're in a copycat campaign, or clear it if we started playing.
     application.setHocCampaign(if @campaign?.get('type') is 'hoc' then @campaign.get('slug') else '')
@@ -834,10 +849,6 @@

   onSessionsLoaded: (e) ->
     return if @editorMode
-    for session in @sessions.models
-      unless @levelStatusMap[session.get('levelID')] is 'complete'  # Don't overwrite a complete session with an incomplete one
-        @levelStatusMap[session.get('levelID')] = if session.get('state')?.complete then 'complete' else 'started'
-      @levelDifficultyMap[session.get('levelID')] = session.get('state').difficulty if session.get('state')?.difficulty
     @render()
     @loadUserPollsRecord() unless me.get('anonymous') or window.serverConfig.picoCTF


prefer results close to the viewbox for geocoding

--- app/assets/javascripts/models/plan.js.coffee
+++ app/assets/javascripts/models/plan.js.coffee
@@ -28,9 +28,11 @@
     numItineraries: @get('desired_itineraries')

   geocode: (query, callback) ->
+    bounds = Transit.map.getBounds()
     $.get '/nominatim/v1/search'
       format: 'json'
       countrycodes: 'US'
+      viewbox: bounds.toBBoxString()
       q: query
     .success(callback)
     .error ->


Avoid load null user

Avoid load null user

--- app/coffee/modules/user-settings/main.coffee
+++ app/coffee/modules/user-settings/main.coffee
@@ -54,9 +54,9 @@

         if !@scope.user
             @errorHandlingService.permissionDenied()
-
-        @scope.lang = @getLan()
-        @scope.theme = @getTheme()
+        else
+            @scope.lang = @getLan()
+            @scope.theme = @getTheme()

         maxFileSize = @config.get("maxUploadFileSize", null)
         if maxFileSize


Add clearing ghost tabs on timeout

Add clearing ghost tabs on timeout

--- src/smc-webapp/project_store.coffee
+++ src/smc-webapp/project_store.coffee
@@ -151,6 +151,8 @@
             @clear_ghost_file_tabs()
         else
             @add_a_ghost_file_tab()
+        window.clearTimeout(@last_close_timer)
+        @last_close_timer = window.setTimeout(@clear_ghost_file_tabs, 5000)
         @close_file(path)

     # Expects one of ['files', 'new', 'log', 'search', 'settings']


hotfix -- disable password reset

hotfix -- disable password reset

--- src/smc-webapp/redux_account.coffee
+++ src/smc-webapp/redux_account.coffee
@@ -107,6 +107,11 @@
         )

     forgot_password: (email) =>
+        # temporary hotfix while sendgrid is broken
+        @setState
+            forgot_password_error : "Automatic password reset is currently not available. Please email help@sagemath.com and request a manual password reset."
+        return
+
         webapp_client.forgot_password
             email_address : email
             cb : (err, mesg) =>


Call the callback only once

Call the callback only once

--- src/lib/Network.coffee
+++ src/lib/Network.coffee
@@ -556,7 +556,8 @@
       socket.connect()
       socket.send()
       socket.disconnect()
-      do callback
+
+    do callback

   start: (callback) ->
     unless callback


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/string_extensions.coffee
+++ src/string_extensions.coffee
@@ -58,7 +58,7 @@
   this.charAt(0).toUpperCase() + this.substring(1).toLowerCase()

 ( ->
-root = e
+root = exports ? this
 ###*
 Return the class or constant named in this string.


Properly update updateable status after package install

--- conda_ui/static/conda_ui/installed.coffee
+++ conda_ui/static/conda_ui/installed.coffee
@@ -28,6 +28,7 @@
             @stopListening @pkgs
             @listenTo(@pkgs, 'sync', () => @update())
             @listenTo(@envs, 'activate', () => @update())
+            @listenTo(@envs, 'sync', () => @update())
             @envs.once('sync', () => @update())

         update: () ->


fix #34

fix #34

--- src/lib/fixture.coffee
+++ src/lib/fixture.coffee
@@ -26,9 +26,8 @@
         debugMode = options.debug ? !!@facade.debug
         if debugMode
             require('debug').enable('base-domain:fixture')
-            debug = require('debug')('base-domain:fixture')
-        else
-            debug = ->
+
+        debug = require('debug')('base-domain:fixture')

         # loading model files
         @fxModelMap = {}


Use the newer Node.js binding for 'vm'.

Use the newer Node.js binding for 'vm'.

--- repl.coffee
+++ repl.coffee
@@ -7,7 +7,7 @@
 # Require the **coffee-script** module to get access to the compiler.
 CoffeeScript = require './coffee-script'
 readline     = require 'readline'
-Script       = process.binding('evals').Script
+{Script}     = require 'vm'

 # REPL Setup


comment explaining why we dont throw on supervisor update

--- src/supervisor-update.coffee
+++ src/supervisor-update.coffee
@@ -123,3 +123,6 @@
 			startNewSupervisor(currentSupervisor)
 		.catch (err) ->
 			utils.mixpanelTrack('Supervisor update failed', error: err)
+			# The error here is intentionally not propagated further up the chain,
+			# because the supervisor-update module internally handles update failures
+			# and makes sure that ill updates do not affect the rest of the system.


Remove trailing commas.

Remove trailing commas.

--- src/supervisor-update.coffee
+++ src/supervisor-update.coffee
@@ -41,8 +41,8 @@
 			Image: supervisorTag
 			Cmd: ['/start']
 			Volumes:
-				'/boot/config.json': '/mnt/mmcblk0p1/config.json',
-				'/data': '/var/lib/docker/data',
+				'/boot/config.json': '/mnt/mmcblk0p1/config.json'
+				'/data': '/var/lib/docker/data'
 				'/run/docker.sock': '/var/run/docker.sock'
 			Env: [
 				'API_ENDPOINT=' + config.API_ENDPOINT


fix typo in a comment

fix typo in a comment

--- src/smc-hub/kucalc/quota.coffee
+++ src/smc-hub/kucalc/quota.coffee
@@ -88,7 +88,7 @@
     #            quota.privileged = true
     #            break

-    # little helper to caluclate the quotas, contributions, and limits
+    # Little helper to calculate the quotas, contributions, and limits.
     # name: of the computed quota, upgrade the quota config key,
     # parse_num for converting numbers, and factor for conversions
     calc = (name, upgrade, parse_num, factor = 1) ->


update document

update document

--- src/scripts/fgb.coffee
+++ src/scripts/fgb.coffee
@@ -5,6 +5,9 @@
 #   HUBOT_FGB_BACKLOG_SPACE_ID
 #   HUBOT_FGB_BACKLOG_USERNAME
 #   HUBOT_FGB_BACKLOG_API_KEY
+#   HUBOT_FGB_GITHUB_TOKEN
+#   HUBOT_FGB_MERGE_DEFAULT_USERNAME
+#   HUBOT_FGB_MERGE_TIMEOUT
 #   HUBOT_FGB_PROJECTS
 #   HUBOT_FGB_USERS
 #


Bugfixes to address XML formatting

Bugfixes to address XML formatting

Repo version throws a fatal error as of March 2013. Needs array indices to get correct data from XML/DOM.

--- src/scripts/mta.coffee
+++ src/scripts/mta.coffee
@@ -30,12 +30,12 @@
         throw err
       re = new RegExp(msg.match[1], 'gi')
       if msg.match[1].length is 1 or msg.match[1].toUpperCase() is 'SIR'
-        for k in res.service.subway.line
-          str = k.name
+        for k in res.service.subway[0].line
+          str = k.name[0]
           if str.match(re)
-            if k.status == 'GOOD SERVICE'
+            if k.status[0] == 'GOOD SERVICE'
               msg.send 'the ' + str + ' train is ok!'
-            else if k.status == 'PLANNED WORK'
+            else if k.status[0] == 'PLANNED WORK'
               msg.send 'heads up, the ' + str + ' train has planned work.'
             else
               msg.send 'the ' + str + ' train is all kinds of messed up'


Separate zoom and drag mouse actions

Separate zoom and drag mouse actions

The previous attempts did not take into account that the scale and translate were both being driven by the same event handler.  With this commit, I seperated them out logically, but not
into their own handlers.  I think that multiple handlers (one for dragging and one for zooming) are a bit overkill since they affect the same attribute on the same element.  It might be
best to change the name of the function from zoom() to translate for clarity.  I've left it as is for now, but see it as a likely change in the future but it will be left until I've
asked Huston why they were in the same function in the first place, just in case.

Adjust zoom amount

Purely Aesthetic

--- app/scripts/alchemy/core/interactions.coffee
+++ app/scripts/alchemy/core/interactions.coffee
@@ -170,9 +170,15 @@
                             currTransform = graph.attr("transform")
                                         .match(/(-*\d+\.*\d*)/g)
                                         .map( (a) -> return parseFloat(a) )
-                            graph
-                              .attr("transform","translate(#{ d3.event.translate}) scale(#{ d3.event.scale })")
-
+
+                            if d3.event.sourceEvent.type == "mousemove"
+                                graph.attr("transform", "translate(#{ d3.event.translate }) scale(#{ currTransform[2] })")
+
+                            else if d3.event.sourceEvent.type == "wheel"
+                                scrollDirection = d3.event.sourceEvent.wheelDelta / Math.abs(d3.event.sourceEvent.wheelDelta)
+                                graph
+                                  .attr("transform", "translate(#{ currTransform[0] },#{ currTransform[1] })
+                                                      scale(#{ currTransform[2] + (scrollDirection * 0.2) })")
                             return

     clickZoom:  ()->
@@ -184,8 +190,8 @@
                     graph
                         .attr("transform", ->
                             if id == "zoom-in"
-                                return "translate(#{ currTransform[0] },#{ currTransform[1] }) scale(#{ currTransform[2]+0.3 })"
+                                return "translate(#{ currTransform[0] },#{ currTransform[1] }) scale(#{ currTransform[2]+0.2 })"

                             else
-                                return "translate(#{ currTransform[0] },#{ currTransform[1] }) scale(#{ currTransform[2]-0.3 })"
+                                return "translate(#{ currTransform[0] },#{ currTransform[1] }) scale(#{ currTransform[2]-0.2 })"
                             )


Guard against no active item

Guard against no active item

--- lib/selection-count-view.coffee
+++ lib/selection-count-view.coffee
@@ -3,9 +3,9 @@
   initialize: (@statusBar) ->
     @classList.add('selection-count', 'inline-block')

-    @activeItemSubscription = atom.workspace.onDidChangeActivePaneItem (textEditor) =>
+    @activeItemSubscription = atom.workspace.onDidChangeActivePaneItem (activeItem) =>
       @selectionSubscription?.dispose()
-      @selectionSubscription = textEditor.onDidChangeSelectionRange? => @updateCount()
+      @selectionSubscription = activeItem?.onDidChangeSelectionRange? => @updateCount()

     @selectionSubscription = atom.workspace.getActiveTextEditor()?.onDidChangeSelectionRange => @updateCount()
     @updateCount()


Hotfix - Win environment

Hotfix - Win environment

--- src/tasks/_util.coffee
+++ src/tasks/_util.coffee
@@ -3,7 +3,7 @@

 #-- sep
 sep = require('path').sep
-util.sep = (path) -> path.replace /\//g, sep
+util.sep = (path) -> path

 #-- paths


:art:

:art:

--- src/text-buffer.coffee
+++ src/text-buffer.coffee
@@ -13,38 +13,40 @@
 Patch = require './patch'
 {spliceArray, newlineRegex} = require './helpers'

-class SearchMatch
-  Object.defineProperty @prototype, "range", get: ->
-    matchStartIndex = @match.index
-    matchEndIndex = matchStartIndex + @getMatchLength()
-
-    startPosition = @buffer.positionForCharacterIndex(matchStartIndex + @lengthDelta)
-    endPosition = @buffer.positionForCharacterIndex(matchEndIndex + @lengthDelta)
-
-    new Range(startPosition, endPosition)
+class SearchResultCallback
+  Object.defineProperty @::, "range", get: -> @getRange()

   constructor: (@buffer, @match, @lengthDelta) ->
     @stopped = false
     @replacementText = null
     @matchText = @match[0]

-  getMatchLength: ->
-    @matchText.length
-
   hasReplacedText: ->
     @replacementText?

   getReplacementText: ->
     @replacementText

+  getReplacementDelta: ->
+    @getReplacementText().length - @matchText.length
+
   replace: (text) =>
     @replacementText = text

   stop: =>
     @stopped = true

-  keepLooping: =>
-    @stopped isnt true
+  keepLooping: ->
+    @stopped is false
+
+  getRange: ->
+    matchStartIndex = @match.index
+    matchEndIndex = matchStartIndex + @matchText.length
+
+    startPosition = @buffer.positionForCharacterIndex(matchStartIndex + @lengthDelta)
+    endPosition = @buffer.positionForCharacterIndex(matchEndIndex + @lengthDelta)
+
+    new Range(startPosition, endPosition)

 class TransactionAbortedError extends Error
   constructor: -> super
@@ -1000,15 +1002,14 @@

     matches.reverse() if reverse
     for match in matches
-      result = new SearchMatch(this, match, lengthDelta)
-
-      iterator(result)
-
-      if result.hasReplacedText()
-        @setTextInRange(result.range, result.getReplacementText())
-        lengthDelta += result.getReplacementText().length - result.getMatchLength() unless reverse
-
-      break unless global and result.keepLooping()
+      callback = new SearchResultCallback(this, match, lengthDelta)
+      iterator(callback)
+
+      if callback.hasReplacedText()
+        @setTextInRange(callback.getRange(), callback.getReplacementText())
+        lengthDelta += callback.getReplacementDelta() unless reverse
+
+      break unless global and callback.keepLooping()
     return

   # Public: Scan regular expression matches in a given range in reverse order,


:memo: updating docs for getCursorAtScreenPosition - can also take an {Array}

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -1563,7 +1563,7 @@

   # Essential: Get a {Cursor} at given screen coordinates {Point}
   #
-  #* `position` A {Point}
+  # * `position` A {Point} or {Array} of `[row, column]`
   #
   # Returns the first matched {Cursor} or undefined
   getCursorAtScreenPosition: (position) ->


Added .getElement to TextEditor.

Added .getElement to TextEditor.

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -11,6 +11,7 @@
 TextMateScopeSelector = require('first-mate').ScopeSelector
 {Directory} = require "pathwatcher"
 GutterContainer = require './gutter-container'
+TextEditorElement = require './text-editor-element'

 # Essential: This class represents all essential editing state for a single
 # {TextBuffer}, including cursor and selection positions, folds, and soft wraps.
@@ -61,6 +62,10 @@
   suppressSelectionMerging: false
   selectionFlashDuration: 500
   gutterContainer: null
+  editorElement: null
+
+  Object.defineProperty @prototype, "element",
+    get: -> @getElement()

   @deserialize: (state, atomEnvironment) ->
     try
@@ -3142,6 +3147,10 @@
   Section: TextEditor Rendering
   ###

+  # Get the Element for the editor.
+  getElement: ->
+    @editorElement ?= new TextEditorElement().initialize(this, atom)
+
   # Essential: Retrieves the greyed out placeholder of a mini editor.
   #
   # Returns a {String}.


Add assertion about removing the last selection

--- src/text-editor.coffee
+++ src/text-editor.coffee
@@ -2245,6 +2245,7 @@
   # Remove the given selection.
   removeSelection: (selection) ->
     _.remove(@selections, selection)
+    atom.assert @selections.length > 0, "Removed last selection"
     @emit 'selection-removed', selection if includeDeprecatedAPIs
     @emitter.emit 'did-remove-selection', selection


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- src/text_effect.coffee
+++ src/text_effect.coffee
@@ -3,10 +3,8 @@

     # adds a TextEffect to the engine
     engine.add 'TextEffect'
-      x: 50
-      y: 50
-
-
+      x: 60
+      y: 100

 @name TextEffect
 @constructor


levels and default game states

levels and default game states

--- src/text_screen.coffee
+++ src/text_screen.coffee
@@ -1,4 +1,5 @@
 GameOver = (I={}) ->
+
   self = GameState(I).extend
     drawTitleText: (canvas, titleText, subtitleText) ->
       canvas.fillColor 'white'


Improve logging

Improve logging

--- file/acp/be.bastelstu.wcf.nodePush/app.coffee
+++ file/acp/be.bastelstu.wcf.nodePush/app.coffee
@@ -137,12 +137,14 @@
 for signal in [ 'SIGINT', 'SIGTERM', 'SIGHUP' ]
 	do (signal) ->
 		process.once signal, ->
+			logger.log "info", "Received:", signal
 			do cleanup
 			do process.exit

 for signal in [ 'SIGUSR2' ]
 	do (signal) ->
 		process.once signal, ->
+			logger.log "info", "Received:", signal
 			do cleanup
 			process.kill process.pid, signal


updating cli header and properties

updating cli header and properties

--- src/toaster/cli.coffee
+++ src/toaster/cli.coffee
@@ -14,6 +14,7 @@
 		usage += "  toaster -n myawsomeapp   (#{'required'.red})\n"
 		usage += "  toaster -i [myawsomeapp] (#{'optional'.green})\n"
 		usage += "  toaster -w [myawsomeapp] (#{'optional'.green})"
+		usage += "  toaster -wd [myawsomeapp] (#{'optional'.green})"

 		@argv = (@opts = optimist.usage( usage )
     		.alias('n', 'new')
@@ -23,16 +24,12 @@
 			.describe('i', "Create a config (toaster.coffee) file")

 			.alias('w', 'watch')
-			.string( 'w' )
 			.describe('w', "Start watching/compiling your project")

 			.alias('c', 'compile')
-			.string( 'c' )
 			.describe('c', "Compile the entire project, without watching it.")

 			.alias('d', 'debug')
-			.boolean( 'd' )
-			.default('d', false )
 			.describe('d', 'Debug mode (compile js files individually)')

 			.alias('j', 'config')


Make components read only for anonymous users

--- app/views/editor/level/component/edit.coffee
+++ app/views/editor/level/component/edit.coffee
@@ -50,6 +50,7 @@
       supermodel: @supermodel
       schema: schema
       data: data
+      readonly: @me.get('anonymous')
       callbacks: {change: @onComponentSettingsEdited}
     @componentSettingsTreema = @$el.find('#edit-component-treema').treema treemaOptions
     @componentSettingsTreema.build()
@@ -67,6 +68,7 @@
       supermodel: @supermodel
       schema: LevelComponent.schema.properties.configSchema
       data: @levelComponent.get 'configSchema'
+      readonly: @me.get('anonymous')
       callbacks: {change: @onConfigSchemaEdited}
     @configSchemaTreema = @$el.find('#config-schema-treema').treema treemaOptions
     @configSchemaTreema.build()
@@ -83,6 +85,7 @@
     editorEl = $('

').text(@levelComponent.get('code')).addClass('inner-editor')
     @$el.find('#component-code-editor').empty().append(editorEl)
     @editor = ace.edit(editorEl[0])
+    @editor.setReadOnly(me.get('anonymous'))
     session = @editor.getSession()
     session.setMode 'ace/mode/coffee'
     session.setTabSize 2


fixed a typo in the navigation component

--- library/gearbox/navigate.coffee
+++ library/gearbox/navigate.coffee
@@ -80,7 +80,7 @@
         assert _.isFunction externed = external implement
         assert _.isObject externed.remote.meta ?= Object()
         assert ptr = endpoint: endpoint, options: options
-        logger.silly m.grey, endpoint.underline, idenity
+        logger.silly m.grey, endpoint.underline, identify
         _.extend externed.remote.meta, ptr; externed

     # This is an external autocall routine that when invoked on the


Fixed Jasmine tests in light of Logger changes, and wrote test to cover case where data passed is not a dictionary

--- common/static/coffee/spec/logger_spec.coffee
+++ common/static/coffee/spec/logger_spec.coffee
@@ -3,10 +3,15 @@
     expect(window.log_event).toBe Logger.log

   describe 'log', ->
-    it 'sends an event to Segment.io, if the event is whitelisted', ->
+    it 'sends an event to Segment.io, if the event is whitelisted and the data is not a dictionary', ->
       spyOn(analytics, 'track')
       Logger.log 'seq_goto', 'data'
-      expect(analytics.track).toHaveBeenCalledWith 'seq_goto', 'data'
+      expect(analytics.track).toHaveBeenCalledWith 'seq_goto', value: 'data'
+
+    it 'sends an event to Segment.io, if the event is whitelisted and the data is a dictionary', ->
+      spyOn(analytics, 'track')
+      Logger.log 'seq_goto', value: 'data'
+      expect(analytics.track).toHaveBeenCalledWith 'seq_goto', value: 'data'

     it 'send a request to log event', ->
       spyOn $, 'getWithPrefix'


PassNextKey; exclude single-character mappings.

Consider:

    map q passNextKey

This works fine in normal mode.  However, in insert mode, when the user
types "q" they expect "q" to be input.  Any other behaviour would be
pretty weird.

Here, we filter out such mappings before the front end sees them.  So,
with:

    map q passNextKey
    map <c-]> passNextKey

both mappings work in normal mode, but only the second works in insert
mode.  This is probably the behaviour likely to cause least user
confusion.

--- background_scripts/commands.coffee
+++ background_scripts/commands.coffee
@@ -68,9 +68,11 @@
             @keyToCommandRegistry = {}

     # Push the key mapping for passNextKey into Settings so that it's available in the front end for insert
+    # mode.  We exclude single-key mappings (that is, printable keys) because when users press printable keys
+    # in insert mode they expect the character to be input, not to be droppped into some special Vimium
     # mode.
     Settings.set "passNextKeyKeys",
-      (key for own key of @keyToCommandRegistry when @keyToCommandRegistry[key].command == "passNextKey")
+      (key for own key of @keyToCommandRegistry when @keyToCommandRegistry[key].command == "passNextKey" and 1 < key.length)

   clearKeyMappingsAndSetDefaults: ->
     @keyToCommandRegistry = {}


fixed typo

fixed typo

--- js-library/app/js/util/scrollspeed.coffee
+++ js-library/app/js/util/scrollspeed.coffee
@@ -37,7 +37,7 @@

 $(window).on 'scroll', check_scrolling_speed

-# flag to ensure we only fire on state chaneg
+# flag to ensure we only fire on state change
 currently_fast_scrolling = false
 Factlink.el.on
   fast_scrolling: =>