Show who is online

This commit is contained in:
James Allen 2014-07-17 15:25:22 +01:00
parent 3995de3cfc
commit 37a12e88c1
9 changed files with 168 additions and 71 deletions

View file

@ -13,62 +13,64 @@ FOUR_DAYS_IN_S = ONE_DAY_IN_S * 4
USER_TIMEOUT_IN_S = ONE_HOUR_IN_S USER_TIMEOUT_IN_S = ONE_HOUR_IN_S
buildProjectSetKey = (project_id)-> return "users_in_project:#{project_id}" buildProjectSetKey = (project_id)-> return "clients_in_project:#{project_id}"
buildUserKey = (project_id, user_id)-> return "connected_user:#{project_id}:#{user_id}" buildUserKey = (project_id, client_id)-> return "connected_user:#{project_id}:#{client_id}"
module.exports = module.exports =
markUserAsConnected: (project_id, user_id, callback = (err)->)-> markUserAsConnected: (project_id, client_id, user, callback = (err)->)->
logger.log project_id:project_id, user_id:user_id, "marking user as connected" logger.log project_id:project_id, client_id:client_id, "marking user as connected"
multi = rclient.multi() multi = rclient.multi()
multi.sadd buildProjectSetKey(project_id), user_id multi.sadd buildProjectSetKey(project_id), client_id
multi.expire buildProjectSetKey(project_id), FOUR_DAYS_IN_S multi.expire buildProjectSetKey(project_id), FOUR_DAYS_IN_S
multi.hset buildUserKey(project_id, user_id), "connected_at", new Date() multi.hset buildUserKey(project_id, client_id), "connected_at", Date.now()
multi.expire buildUserKey(project_id, user_id), USER_TIMEOUT_IN_S multi.hset buildUserKey(project_id, client_id), "user_id", user._id
multi.hset buildUserKey(project_id, client_id), "first_name", user.first_name
multi.hset buildUserKey(project_id, client_id), "last_name", user.last_name
multi.hset buildUserKey(project_id, client_id), "email", user.email
multi.expire buildUserKey(project_id, client_id), USER_TIMEOUT_IN_S
multi.exec (err)-> multi.exec (err)->
if err? if err?
logger.err err:err, project_id:project_id, user_id:user_id, "problem marking user as connected" logger.err err:err, project_id:project_id, client_id:client_id, "problem marking user as connected"
callback(err) callback(err)
markUserAsDisconnected: (project_id, user_id, callback)-> markUserAsDisconnected: (project_id, client_id, callback)->
logger.log project_id:project_id, user_id:user_id, "marking user as disconnected" logger.log project_id:project_id, client_id:client_id, "marking user as disconnected"
multi = rclient.multi() multi = rclient.multi()
multi.srem buildProjectSetKey(project_id), user_id multi.srem buildProjectSetKey(project_id), client_id
multi.expire buildProjectSetKey(project_id), FOUR_DAYS_IN_S multi.expire buildProjectSetKey(project_id), FOUR_DAYS_IN_S
multi.del buildUserKey(project_id, user_id) multi.del buildUserKey(project_id, client_id)
multi.exec callback multi.exec callback
_getConnectedUser: (project_id, user_id, callback)-> _getConnectedUser: (project_id, client_id, callback)->
rclient.hgetall buildUserKey(project_id, user_id), (err, result)-> rclient.hgetall buildUserKey(project_id, client_id), (err, result)->
if !result? if !result?
result = result =
connected : false connected : false
user_id:user_id client_id:client_id
else else
result.connected = true result.connected = true
result.user_id = user_id result.client_id = client_id
if result.cursorData? if result.cursorData?
result.cursorData = JSON.parse(result.cursorData) result.cursorData = JSON.parse(result.cursorData)
result.email = result.cursorData.email
result.name = result.cursorData.name
callback err, result callback err, result
setUserCursorPosition: (project_id, user_id, cursorData, callback)-> setUserCursorPosition: (project_id, client_id, cursorData, callback)->
multi = rclient.multi() multi = rclient.multi()
multi.hset buildUserKey(project_id, user_id), "cursorData", JSON.stringify(cursorData) multi.hset buildUserKey(project_id, client_id), "cursorData", JSON.stringify(cursorData)
multi.expire buildUserKey(project_id, user_id), USER_TIMEOUT_IN_S multi.expire buildUserKey(project_id, client_id), USER_TIMEOUT_IN_S
multi.exec callback multi.exec callback
getConnectedUsers: (project_id, callback)-> getConnectedUsers: (project_id, callback)->
self = @ self = @
rclient.smembers buildProjectSetKey(project_id), (err, results)-> rclient.smembers buildProjectSetKey(project_id), (err, results)->
jobs = results.map (user_id)-> jobs = results.map (client_id)->
(cb)-> (cb)->
self._getConnectedUser(project_id, user_id, cb) self._getConnectedUser(project_id, client_id, cb)
async.series jobs, (err, users)-> async.series jobs, (err, users)->
users = _.filter users, (user)-> users = _.filter users, (user)->
user.connected user.connected

View file

@ -59,16 +59,14 @@ module.exports = EditorController =
callback null, ProjectEditorHandler.buildProjectModelView(project), privilegeLevel, EditorController.protocolVersion callback null, ProjectEditorHandler.buildProjectModelView(project), privilegeLevel, EditorController.protocolVersion
# can be done affter the connection has happened # can be done affter the connection has happened
EditorRealTimeController.emitToRoom(project_id, "ConnectedUsers.userConnected", user) ConnectedUsersManager.markUserAsConnected project_id, client.id, user, ->
ConnectedUsersManager.markUserAsConnected project_id, user._id, ->
leaveProject: (client, user) -> leaveProject: (client, user) ->
self = @ self = @
client.get "project_id", (error, project_id) -> client.get "project_id", (error, project_id) ->
return if error? or !project_id? return if error? or !project_id?
EditorRealTimeController.emitToRoom(project_id, "clientTracking.clientDisconnected", client.id) EditorRealTimeController.emitToRoom(project_id, "clientTracking.clientDisconnected", client.id)
EditorRealTimeController.emitToRoom(project_id, "ConnectedUsers.userDissconected", user) ConnectedUsersManager.markUserAsDisconnected project_id, client.id, ->
ConnectedUsersManager.markUserAsDisconnected project_id, user._id, ->
logger.log user_id:user._id, project_id:project_id, "user leaving project" logger.log user_id:user._id, project_id:project_id, "user leaving project"
self.flushProjectIfEmpty(project_id) self.flushProjectIfEmpty(project_id)
@ -126,7 +124,11 @@ module.exports = EditorController =
cursorData.email = email if email? cursorData.email = email if email?
if first_name? and last_name? if first_name? and last_name?
cursorData.name = first_name + " " + last_name cursorData.name = first_name + " " + last_name
ConnectedUsersManager.setUserCursorPosition(project_id, user_id, cursorData, ->) ConnectedUsersManager.setUserCursorPosition(project_id, client.id, {
row: cursorData.row,
column: cursorData.column,
doc_id: cursorData.doc_id
}, ->)
else else
cursorData.name = "Anonymous" cursorData.name = "Anonymous"
EditorRealTimeController.emitToRoom(project_id, "clientTracking.clientUpdated", cursorData) EditorRealTimeController.emitToRoom(project_id, "clientTracking.clientUpdated", cursorData)

View file

@ -39,6 +39,20 @@ header.toolbar.toolbar-header(ng-cloak, ng-hide="state.loading")
i.fa.fa-pencil i.fa.fa-pencil
.toolbar-right .toolbar-right
span.online-users(
ng-show="onlineUsersArray.length > 0"
ng-controller="OnlineUsersController"
)
span.online-user(
ng-repeat="user in onlineUsersArray",
ng-style="{ 'background-color': 'hsl({{ getHueForUserId(user.user_id) }}, 100%, 50%)' }",
popover="{{ user.name }}"
popover-placement="bottom"
popover-append-to-body="true"
popover-trigger="mouseenter"
ng-click="gotoUser(user)"
) {{ user.name.slice(0,1) }}
a.btn.btn-full-height( a.btn.btn-full-height(
href, href,
ng-if="permissions.admin", ng-if="permissions.admin",

View file

@ -1,30 +1,63 @@
define [ define [
"libs/md5" "libs/md5"
"ide/online-users/controllers/OnlineUsersController"
], () -> ], () ->
class OnlineUsersManager class OnlineUsersManager
constructor: (@ide, @$scope) -> constructor: (@ide, @$scope) ->
@$scope.onlineUsers = {} @$scope.onlineUsers = {}
@$scope.onlineUserCursorHighlights = {} @$scope.onlineUserCursorHighlights = {}
@$scope.onlineUsersArray = []
@$scope.$on "cursor:editor:update", (event, position) => @$scope.$on "cursor:editor:update", (event, position) =>
@sendCursorPositionUpdate(position) @sendCursorPositionUpdate(position)
@$scope.$on "project:joined", () =>
ide.$http
.get "/project/#{@ide.$scope.project._id}/connected_users"
.success (connectedUsers) =>
@$scope.onlineUsers = {}
for user in connectedUsers or []
if user.client_id == @ide.socket.socket.sessionid
# Don't store myself
continue
# Store data in the same format returned by clientTracking.clientUpdated
@$scope.onlineUsers[user.client_id] = {
id: user.client_id
user_id: user.user_id
email: user.email
name: "#{user.first_name} #{user.last_name}"
doc_id: user.cursorData?.doc_id
row: user.cursorData?.row
column: user.cursorData?.column
}
@refreshOnlineUsers()
@ide.socket.on "clientTracking.clientUpdated", (client) => @ide.socket.on "clientTracking.clientUpdated", (client) =>
if client.id != @ide.socket.socket.sessionid # Check it's not me! if client.id != @ide.socket.socket.sessionid # Check it's not me!
@$scope.$apply () => @$scope.$apply () =>
@$scope.onlineUsers[client.id] = client @$scope.onlineUsers[client.id] = client
@updateCursorHighlights() @refreshOnlineUsers()
@ide.socket.on "clientTracking.clientDisconnected", (client_id) => @ide.socket.on "clientTracking.clientDisconnected", (client_id) =>
@$scope.$apply () => @$scope.$apply () =>
delete @$scope.onlineUsers[client_id] delete @$scope.onlineUsers[client_id]
@updateCursorHighlights() @refreshOnlineUsers()
@$scope.getHueForUserId = (user_id) =>
@getHueForUserId(user_id)
updateCursorHighlights: () -> refreshOnlineUsers: () ->
@$scope.onlineUsersArray = []
for client_id, user of @$scope.onlineUsers
if user.doc_id?
user.doc = @ide.fileTreeManager.findEntityById(user.doc_id)
@$scope.onlineUsersArray.push user
@$scope.onlineUserCursorHighlights = {} @$scope.onlineUserCursorHighlights = {}
for client_id, client of @$scope.onlineUsers for client_id, client of @$scope.onlineUsers
doc_id = client.doc_id doc_id = client.doc_id
continue if !doc_id? continue if !doc_id? or !client.row? or !client.column?
@$scope.onlineUserCursorHighlights[doc_id] ||= [] @$scope.onlineUserCursorHighlights[doc_id] ||= []
@$scope.onlineUserCursorHighlights[doc_id].push { @$scope.onlineUserCursorHighlights[doc_id].push {
label: client.name label: client.name

View file

@ -0,0 +1,7 @@
define [
"base"
], (App) ->
App.controller "OnlineUsersController", ($scope, ide) ->
$scope.gotoUser = (user) ->
if user.doc? and user.row?
ide.editorManager.openDoc(user.doc, gotoLine: user.row + 1)

View file

@ -8,6 +8,7 @@
@import "./editor/binary-file.less"; @import "./editor/binary-file.less";
@import "./editor/search.less"; @import "./editor/search.less";
@import "./editor/publish-template.less"; @import "./editor/publish-template.less";
@import "./editor/online-users.less";
.full-size { .full-size {
position: absolute; position: absolute;

View file

@ -0,0 +1,14 @@
.online-users {
.online-user {
background-color: rgb(0, 170, 255);
width: 24px;
display: inline-block;
height: 24px;
margin-right: 8px;
text-align: center;
color: white;
text-transform: uppercase;
border-radius: 3px;
cursor: pointer;
}
}

View file

@ -36,8 +36,14 @@ describe "ConnectedUsersManager", ->
"logger-sharelatex": log:-> "logger-sharelatex": log:->
"redis": createClient:=> "redis": createClient:=>
return @rClient return @rClient
@user_id = "32132132" @client_id = "32132132"
@project_id = "dskjh2u21321" @project_id = "dskjh2u21321"
@user = {
_id: "user-id-123"
first_name: "Joe"
last_name: "Bloggs"
email: "joe@example.com"
}
afterEach -> afterEach ->
tk.reset() tk.reset()
@ -47,23 +53,43 @@ describe "ConnectedUsersManager", ->
@rClient.exec.callsArgWith(0) @rClient.exec.callsArgWith(0)
it "should set a key with the date and give it a ttl", (done)-> it "should set a key with the date and give it a ttl", (done)->
@ConnectedUsersManager.markUserAsConnected @project_id, @user_id, (err)=> @ConnectedUsersManager.markUserAsConnected @project_id, @client_id, @user, (err)=>
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@user_id}", "connected_at", new Date()).should.equal true @rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "connected_at", Date.now()).should.equal true
done() done()
it "should push the user_id on to the project list", (done)-> it "should set a key with the user_id", (done)->
@ConnectedUsersManager.markUserAsConnected @project_id, @user_id, (err)=> @ConnectedUsersManager.markUserAsConnected @project_id, @client_id, @user, (err)=>
@rClient.sadd.calledWith("users_in_project:#{@project_id}", @user_id).should.equal true @rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "user_id", @user._id).should.equal true
done()
it "should set a key with the first_name", (done)->
@ConnectedUsersManager.markUserAsConnected @project_id, @client_id, @user, (err)=>
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "first_name", @user.first_name).should.equal true
done()
it "should set a key with the last_name", (done)->
@ConnectedUsersManager.markUserAsConnected @project_id, @client_id, @user, (err)=>
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "last_name", @user.last_name).should.equal true
done()
it "should set a key with the email", (done)->
@ConnectedUsersManager.markUserAsConnected @project_id, @client_id, @user, (err)=>
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "email", @user.email).should.equal true
done()
it "should push the client_id on to the project list", (done)->
@ConnectedUsersManager.markUserAsConnected @project_id, @client_id, @user, (err)=>
@rClient.sadd.calledWith("clients_in_project:#{@project_id}", @client_id).should.equal true
done() done()
it "should add a ttl to the connected user set so it stays clean", (done)-> it "should add a ttl to the connected user set so it stays clean", (done)->
@ConnectedUsersManager.markUserAsConnected @project_id, @user_id, (err)=> @ConnectedUsersManager.markUserAsConnected @project_id, @client_id, @user, (err)=>
@rClient.expire.calledWith("users_in_project:#{@project_id}", 24 * 4 * 60 * 60).should.equal true @rClient.expire.calledWith("clients_in_project:#{@project_id}", 24 * 4 * 60 * 60).should.equal true
done() done()
it "should add a ttl to the connected user so it stays clean", (done)-> it "should add a ttl to the connected user so it stays clean", (done)->
@ConnectedUsersManager.markUserAsConnected @project_id, @user_id, (err)=> @ConnectedUsersManager.markUserAsConnected @project_id, @client_id, @user, (err)=>
@rClient.expire.calledWith("connected_user:#{@project_id}:#{@user_id}", 60 * 60).should.equal true @rClient.expire.calledWith("connected_user:#{@project_id}:#{@client_id}", 60 * 60).should.equal true
done() done()
describe "markUserAsDisconnected", -> describe "markUserAsDisconnected", ->
@ -71,18 +97,18 @@ describe "ConnectedUsersManager", ->
@rClient.exec.callsArgWith(0) @rClient.exec.callsArgWith(0)
it "should remove the user from the set", (done)-> it "should remove the user from the set", (done)->
@ConnectedUsersManager.markUserAsDisconnected @project_id, @user_id, (err)=> @ConnectedUsersManager.markUserAsDisconnected @project_id, @client_id, (err)=>
@rClient.srem.calledWith("users_in_project:#{@project_id}", @user_id).should.equal true @rClient.srem.calledWith("clients_in_project:#{@project_id}", @client_id).should.equal true
done() done()
it "should delete the connected_user string", (done)-> it "should delete the connected_user string", (done)->
@ConnectedUsersManager.markUserAsDisconnected @project_id, @user_id, (err)=> @ConnectedUsersManager.markUserAsDisconnected @project_id, @client_id, (err)=>
@rClient.del.calledWith("connected_user:#{@project_id}:#{@user_id}").should.equal true @rClient.del.calledWith("connected_user:#{@project_id}:#{@client_id}").should.equal true
done() done()
it "should add a ttl to the connected user set so it stays clean", (done)-> it "should add a ttl to the connected user set so it stays clean", (done)->
@ConnectedUsersManager.markUserAsDisconnected @project_id, @user_id, (err)=> @ConnectedUsersManager.markUserAsDisconnected @project_id, @client_id, (err)=>
@rClient.expire.calledWith("users_in_project:#{@project_id}", 24 * 4 * 60 * 60).should.equal true @rClient.expire.calledWith("clients_in_project:#{@project_id}", 24 * 4 * 60 * 60).should.equal true
done() done()
describe "_getConnectedUser", -> describe "_getConnectedUser", ->
@ -90,16 +116,16 @@ describe "ConnectedUsersManager", ->
it "should get the user returning connected if there is a value", (done)-> it "should get the user returning connected if there is a value", (done)->
cursorData = JSON.stringify(cursorData:{row:1}) cursorData = JSON.stringify(cursorData:{row:1})
@rClient.hgetall.callsArgWith(1, null, {connected_at:new Date(), cursorData}) @rClient.hgetall.callsArgWith(1, null, {connected_at:new Date(), cursorData})
@ConnectedUsersManager._getConnectedUser @project_id, @user_id, (err, result)=> @ConnectedUsersManager._getConnectedUser @project_id, @client_id, (err, result)=>
result.connected.should.equal true result.connected.should.equal true
result.user_id.should.equal @user_id result.client_id.should.equal @client_id
done() done()
it "should get the user returning connected if there is a value", (done)-> it "should get the user returning connected if there is a value", (done)->
@rClient.hgetall.callsArgWith(1) @rClient.hgetall.callsArgWith(1)
@ConnectedUsersManager._getConnectedUser @project_id, @user_id, (err, result)=> @ConnectedUsersManager._getConnectedUser @project_id, @client_id, (err, result)=>
result.connected.should.equal false result.connected.should.equal false
result.user_id.should.equal @user_id result.client_id.should.equal @client_id
done() done()
describe "getConnectedUsers", -> describe "getConnectedUsers", ->
@ -108,16 +134,16 @@ describe "ConnectedUsersManager", ->
@users = ["1234", "5678", "9123"] @users = ["1234", "5678", "9123"]
@rClient.smembers.callsArgWith(1, null, @users) @rClient.smembers.callsArgWith(1, null, @users)
@ConnectedUsersManager._getConnectedUser = sinon.stub() @ConnectedUsersManager._getConnectedUser = sinon.stub()
@ConnectedUsersManager._getConnectedUser.withArgs(@project_id, @users[0]).callsArgWith(2, null, {connected:true, user_id:@users[0]}) @ConnectedUsersManager._getConnectedUser.withArgs(@project_id, @users[0]).callsArgWith(2, null, {connected:true, client_id:@users[0]})
@ConnectedUsersManager._getConnectedUser.withArgs(@project_id, @users[1]).callsArgWith(2, null, {connected:false, user_id:@users[1]}) @ConnectedUsersManager._getConnectedUser.withArgs(@project_id, @users[1]).callsArgWith(2, null, {connected:false, client_id:@users[1]})
@ConnectedUsersManager._getConnectedUser.withArgs(@project_id, @users[2]).callsArgWith(2, null, {connected:true, user_id:@users[2]}) @ConnectedUsersManager._getConnectedUser.withArgs(@project_id, @users[2]).callsArgWith(2, null, {connected:true, client_id:@users[2]})
it "should only return the users in the list which are still in redis", (done)-> it "should only return the users in the list which are still in redis", (done)->
@ConnectedUsersManager.getConnectedUsers @project_id, (err, users)=> @ConnectedUsersManager.getConnectedUsers @project_id, (err, users)=>
users.length.should.equal 2 users.length.should.equal 2
users[0].should.deep.equal {user_id:@users[0], connected:true} users[0].should.deep.equal {client_id:@users[0], connected:true}
users[1].should.deep.equal {user_id:@users[2], connected:true} users[1].should.deep.equal {client_id:@users[2], connected:true}
done() done()
describe "setUserCursorPosition", -> describe "setUserCursorPosition", ->
@ -127,13 +153,13 @@ describe "ConnectedUsersManager", ->
@rClient.exec.callsArgWith(0) @rClient.exec.callsArgWith(0)
it "should add the cursor data to the users hash", (done)-> it "should add the cursor data to the users hash", (done)->
@ConnectedUsersManager.setUserCursorPosition @project_id, @user_id, @cursorData, (err)=> @ConnectedUsersManager.setUserCursorPosition @project_id, @client_id, @cursorData, (err)=>
@rClient.hset.calledWith("connected_user:#{@project_id}:#{@user_id}", "cursorData", JSON.stringify(@cursorData)).should.equal true @rClient.hset.calledWith("connected_user:#{@project_id}:#{@client_id}", "cursorData", JSON.stringify(@cursorData)).should.equal true
done() done()
it "should add the ttl on", (done)-> it "should add the ttl on", (done)->
@ConnectedUsersManager.setUserCursorPosition @project_id, @user_id, @cursorData, (err)=> @ConnectedUsersManager.setUserCursorPosition @project_id, @client_id, @cursorData, (err)=>
@rClient.expire.calledWith("connected_user:#{@project_id}:#{@user_id}", 60 * 60).should.equal true @rClient.expire.calledWith("connected_user:#{@project_id}:#{@client_id}", 60 * 60).should.equal true
done() done()

View file

@ -91,7 +91,7 @@ describe "EditorController", ->
@ProjectGetter.populateProjectWithUsers = sinon.stub().callsArgWith(1, null, @project) @ProjectGetter.populateProjectWithUsers = sinon.stub().callsArgWith(1, null, @project)
@AuthorizationManager.setPrivilegeLevelOnClient = sinon.stub() @AuthorizationManager.setPrivilegeLevelOnClient = sinon.stub()
@EditorRealTimeController.emitToRoom = sinon.stub() @EditorRealTimeController.emitToRoom = sinon.stub()
@ConnectedUsersManager.markUserAsConnected.callsArgWith(2) @ConnectedUsersManager.markUserAsConnected.callsArgWith(3)
describe "when authorized", -> describe "when authorized", ->
beforeEach -> beforeEach ->
@ -123,11 +123,8 @@ describe "EditorController", ->
it "should return the project model view, privilege level and protocol version", -> it "should return the project model view, privilege level and protocol version", ->
@callback.calledWith(null, @projectModelView, "owner", @EditorController.protocolVersion).should.equal true @callback.calledWith(null, @projectModelView, "owner", @EditorController.protocolVersion).should.equal true
it "should emit the to the room that the user has connected", ->
@EditorRealTimeController.emitToRoom.calledWith(@project_id, "ConnectedUsers.userConnected", @user).should.equal true
it "should mark the user as connected with the ConnectedUsersManager", -> it "should mark the user as connected with the ConnectedUsersManager", ->
@ConnectedUsersManager.markUserAsConnected.calledWith(@project_id, @user_id).should.equal true @ConnectedUsersManager.markUserAsConnected.calledWith(@project_id, @client.id, @user).should.equal true
describe "when not authorized", -> describe "when not authorized", ->
@ -169,11 +166,8 @@ describe "EditorController", ->
.calledWith(@project_id, "clientTracking.clientDisconnected", @client.id) .calledWith(@project_id, "clientTracking.clientDisconnected", @client.id)
.should.equal true .should.equal true
it "should emit the to the room that the user has connected", ->
@EditorRealTimeController.emitToRoom.calledWith(@project_id, "ConnectedUsers.userDissconected", @user).should.equal true
it "should mark the user as connected with the ConnectedUsersManager", -> it "should mark the user as connected with the ConnectedUsersManager", ->
@ConnectedUsersManager.markUserAsDisconnected.calledWith(@project_id, @user_id).should.equal true @ConnectedUsersManager.markUserAsDisconnected.calledWith(@project_id, @client.id).should.equal true
describe "joinDoc", -> describe "joinDoc", ->
@ -296,7 +290,11 @@ describe "EditorController", ->
@EditorRealTimeController.emitToRoom.calledWith(@project_id, "clientTracking.clientUpdated", @populatedCursorData).should.equal true @EditorRealTimeController.emitToRoom.calledWith(@project_id, "clientTracking.clientUpdated", @populatedCursorData).should.equal true
it "should send the cursor data to the connected user manager", (done)-> it "should send the cursor data to the connected user manager", (done)->
@ConnectedUsersManager.setUserCursorPosition.calledWith(@project_id, @user_id, @populatedCursorData).should.equal true @ConnectedUsersManager.setUserCursorPosition.calledWith(@project_id, @client.id, {
row: @row
column: @column
doc_id: @doc_id
}).should.equal true
done() done()
describe "with an anonymous user", -> describe "with an anonymous user", ->