// TODO: clean up these deep relative paths? jsconfig.json is not working... const Game = require('../../../../server/model/Game'); const globals = require('../../../../server/config/globals'); const EVENT_IDS = globals.EVENT_IDS; const USER_TYPES = globals.USER_TYPES; const STATUS = globals.STATUS; const GameManager = require('../../../../server/modules/singletons/GameManager.js'); const TimerManager = require('../../../../server/modules/singletons/TimerManager.js'); const EventManager = require('../../../../server/modules/singletons/EventManager.js'); const Events = require('../../../../server/modules/Events.js'); const GameStateCurator = require('../../../../server/modules/GameStateCurator.js'); const logger = require('../../../../server/modules/Logger.js')(false); describe('Events', () => { let gameManager, namespace, socket, game, timerManager, eventManager; beforeAll(() => { spyOn(logger, 'debug'); spyOn(logger, 'error'); const inObj = { emit: () => {} }; const toObj = { emit: () => {} }; namespace = { in: () => { return inObj; }, to: () => { return toObj; }, sockets: new Map() }; socket = { id: '123', emit: () => {}, to: () => { return { emit: () => {} }; } }; gameManager = GameManager.instance ? GameManager.instance : new GameManager(logger, globals.ENVIRONMENT.PRODUCTION, 'test'); timerManager = TimerManager.instance ? TimerManager.instance : new TimerManager(logger, 'test'); eventManager = EventManager.instance ? EventManager.instance : new EventManager(logger, 'test'); gameManager.setGameSocketNamespace(namespace); eventManager.publisher = { publish: (...args) => {} }; }); beforeEach(() => { game = new Game( 'ABCD', STATUS.LOBBY, [ { id: 'a', assigned: true, out: true, killed: false, userType: USER_TYPES.MODERATOR }, { id: 'b', gameRole: 'Villager', alignment: 'good', assigned: true, out: false, killed: false, userType: USER_TYPES.PLAYER }, { id: 'c', assigned: true, out: true, killed: false, userType: USER_TYPES.SPECTATOR } ], [{ quantity: 1 }, { quantity: 1 }], false, 'a', true, 'a', new Date().toJSON(), null ); spyOn(namespace, 'to').and.callThrough(); spyOn(namespace, 'in').and.callThrough(); spyOn(socket, 'to').and.callThrough(); spyOn(namespace.in(), 'emit').and.callThrough(); spyOn(namespace.to(), 'emit').and.callThrough(); spyOn(gameManager, 'isGameStartable').and.callThrough(); spyOn(GameStateCurator, 'mapPerson').and.callThrough(); spyOn(eventManager.publisher, 'publish').and.callThrough(); spyOn(eventManager, 'createMessageToPublish').and.stub(); namespace.sockets = new Map(); timerManager.timerThreads = {}; }); describe(EVENT_IDS.PLAYER_JOINED, () => { describe('stateChange', () => { it('should let a player join and mark the game as startable', async () => { await Events.find((e) => e.id === EVENT_IDS.PLAYER_JOINED) .stateChange(game, { id: 'd', assigned: true, userType: USER_TYPES.PLAYER }, { gameManager: gameManager }); expect(gameManager.isGameStartable).toHaveBeenCalled(); expect(game.isStartable).toEqual(true); expect(game.people.find(p => p.id === 'b').assigned).toEqual(true); }); it('should let too many players join and mark the game as NOT startable', async () => { game.people.push({ id: 'e', assigned: true, userType: USER_TYPES.PLAYER }); game.people.push({ id: 'f', assigned: true, userType: USER_TYPES.PLAYER }); await Events.find((e) => e.id === EVENT_IDS.PLAYER_JOINED) .stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager }); expect(gameManager.isGameStartable).toHaveBeenCalled(); expect(game.isStartable).toEqual(false); expect(game.people.find(p => p.id === 'b').assigned).toEqual(true); }); }); describe('communicate', () => { it('should communicate the join event to the rooms sockets, sending the new player', async () => { await Events.find((e) => e.id === EVENT_IDS.PLAYER_JOINED) .communicate(game, { id: 'd', assigned: true, userType: USER_TYPES.PLAYER }, { gameManager: gameManager }); expect(namespace.in).toHaveBeenCalledWith(game.accessCode); expect(namespace.in().emit).toHaveBeenCalledWith( globals.EVENTS.PLAYER_JOINED, GameStateCurator.mapPerson({ id: 'd', assigned: true, userType: USER_TYPES.PLAYER }), game.isStartable ); }); }); }); describe(EVENT_IDS.ADD_SPECTATOR, () => { describe('stateChange', () => { it('should add a spectator', async () => { await Events.find((e) => e.id === EVENT_IDS.ADD_SPECTATOR) .stateChange(game, { id: 'e', name: 'ghost', assigned: true }, { gameManager: gameManager }); expect(gameManager.isGameStartable).not.toHaveBeenCalled(); expect(game.isStartable).toEqual(false); expect(game.people.find(p => p.id === 'e').assigned).toEqual(true); expect(game.people.find(p => p.id === 'e').name).toEqual('ghost'); }); }); describe('communicate', () => { it('should communicate the add spectator event to the rooms sockets, sending the new spectator', async () => { await Events.find((e) => e.id === EVENT_IDS.ADD_SPECTATOR) .communicate(game, { id: 'e', name: 'ghost', assigned: true }, { gameManager: gameManager }); expect(namespace.in).toHaveBeenCalledWith(game.accessCode); expect(namespace.in().emit).toHaveBeenCalledWith( EVENT_IDS.ADD_SPECTATOR, GameStateCurator.mapPerson({ id: 'e', name: 'ghost', assigned: true }) ); }); }); }); describe(EVENT_IDS.FETCH_GAME_STATE, () => { describe('stateChange', () => { it('should find the matching person and update their associated socket id if it is different', async () => { const mockSocket = { join: () => {} }; spyOn(mockSocket, 'join').and.callThrough(); namespace.sockets.set('123', mockSocket); game.people.push({ cookie: 'cookie', socketId: '456' }); await Events.find((e) => e.id === EVENT_IDS.FETCH_GAME_STATE) .stateChange(game, { personId: 'cookie' }, { gameManager: gameManager, requestingSocketId: '123' }); expect(mockSocket.join).toHaveBeenCalledWith(game.accessCode); expect(game.people.find(p => p.socketId === '123')).not.toBeNull(); }); it('should find the matching person and should NOT update their socketId if it is NOT different', async () => { const mockSocket = { join: () => {} }; spyOn(mockSocket, 'join').and.callThrough(); namespace.sockets.set('123', mockSocket); game.people.push({ cookie: 'cookie', socketId: '123' }); await Events.find((e) => e.id === EVENT_IDS.FETCH_GAME_STATE) .stateChange(game, { personId: 'cookie' }, { gameManager: gameManager, requestingSocketId: '123' }); expect(mockSocket.join).not.toHaveBeenCalled(); expect(game.people.find(p => p.socketId === '123')).not.toBeNull(); }); }); describe('communicate', () => { it('should do nothing if the client is not expecting an acknowledgement', async () => { game.people.push({ cookie: 'cookie', socketId: '456' }); const mockSocket = { join: () => {} }; namespace.sockets.set('456', mockSocket); spyOn(GameStateCurator, 'getGameStateFromPerspectiveOfPerson').and.callThrough(); await Events.find((e) => e.id === EVENT_IDS.FETCH_GAME_STATE) .communicate(game, { personId: 'cookie' }, { ackFn: null }); expect(GameStateCurator.getGameStateFromPerspectiveOfPerson).not.toHaveBeenCalled(); }); it('should acknowledge the client with null if a matching person was not found', async () => { game.people.push({ cookie: 'differentCookie', socketId: '456' }); const mockSocket = { join: () => {} }; namespace.sockets.set('456', mockSocket); const vars = { ackFn: () => {}, gameManager: gameManager }; spyOn(vars, 'ackFn').and.callThrough(); spyOn(GameStateCurator, 'getGameStateFromPerspectiveOfPerson').and.callThrough(); await Events.find((e) => e.id === EVENT_IDS.FETCH_GAME_STATE) .communicate(game, { personId: 'cookie' }, vars); expect(GameStateCurator.getGameStateFromPerspectiveOfPerson).not.toHaveBeenCalled(); expect(vars.ackFn).toHaveBeenCalledWith(null); }); it('should acknowledge the client with null if a matching person was found, but the socket is not connected' + ' to the namespace', async () => { game.people.push({ cookie: 'cookie', socketId: '456' }); const mockSocket = { join: () => {} }; namespace.sockets.set('123', mockSocket); const vars = { ackFn: () => {}, gameManager: gameManager }; spyOn(vars, 'ackFn').and.callThrough(); spyOn(GameStateCurator, 'getGameStateFromPerspectiveOfPerson').and.callThrough(); await Events.find((e) => e.id === EVENT_IDS.FETCH_GAME_STATE) .communicate(game, { personId: 'cookie' }, vars); expect(GameStateCurator.getGameStateFromPerspectiveOfPerson).not.toHaveBeenCalled(); expect(vars.ackFn).toHaveBeenCalledWith(null); }); it('should acknowledge the client with the game state if they are found and their socket is connected', async () => { game.people.push({ cookie: 'cookie', socketId: '456' }); const mockSocket = { join: () => {} }; namespace.sockets.set('456', mockSocket); const vars = { ackFn: () => {}, gameManager: gameManager }; spyOn(vars, 'ackFn').and.callThrough(); spyOn(GameStateCurator, 'getGameStateFromPerspectiveOfPerson').and.callThrough(); await Events.find((e) => e.id === EVENT_IDS.FETCH_GAME_STATE) .communicate(game, { personId: 'cookie' }, vars); expect(GameStateCurator.getGameStateFromPerspectiveOfPerson).toHaveBeenCalled(); expect(vars.ackFn).toHaveBeenCalled(); }); }); }); describe(EVENT_IDS.START_GAME, () => { describe('stateChange', () => { it('should start the game', async () => { game.isStartable = true; await Events.find((e) => e.id === EVENT_IDS.START_GAME) .stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager }); expect(game.status).toEqual(STATUS.IN_PROGRESS); }); it('should not start the game if it is not startable', async () => { await Events.find((e) => e.id === EVENT_IDS.START_GAME) .stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager }); expect(game.status).toEqual(STATUS.LOBBY); }); it('should start the game and run the timer if the game has one', async () => { game.isStartable = true; game.hasTimer = true; game.timerParams = {}; spyOn(timerManager, 'runTimer').and.callFake((a, b) => {}); await Events.find((e) => e.id === EVENT_IDS.START_GAME) .stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager, timerManager: timerManager }); expect(game.status).toEqual(STATUS.IN_PROGRESS); expect(game.timerParams.paused).toEqual(true); expect(timerManager.runTimer).toHaveBeenCalled(); }); }); describe('communicate', () => { it('should communicate the start event to the room', async () => { await Events.find((e) => e.id === EVENT_IDS.START_GAME) .communicate(game, {}, { gameManager: gameManager }); expect(namespace.in).toHaveBeenCalledWith(game.accessCode); expect(namespace.in().emit).toHaveBeenCalledWith(EVENT_IDS.START_GAME); }); it('should communicate the start event to the room and acknowledge the client', async () => { const vars = { ackFn: () => {}, gameManager: gameManager }; spyOn(vars, 'ackFn').and.callThrough(); await Events.find((e) => e.id === EVENT_IDS.START_GAME) .communicate(game, {}, vars); expect(namespace.in).toHaveBeenCalledWith(game.accessCode); expect(namespace.in().emit).toHaveBeenCalledWith(EVENT_IDS.START_GAME); expect(vars.ackFn).toHaveBeenCalled(); }); }); }); describe(EVENT_IDS.KILL_PLAYER, () => { describe('stateChange', () => { it('should kill the indicated player', async () => { await Events.find((e) => e.id === EVENT_IDS.KILL_PLAYER) .stateChange(game, { personId: 'b' }, { gameManager: gameManager }); const person = game.people.find(p => p.id === 'b'); expect(person.userType).toEqual(USER_TYPES.KILLED_PLAYER); expect(person.out).toBeTrue(); expect(person.killed).toBeTrue(); }); it('should not kill the player if they are already out', async () => { await Events.find((e) => e.id === EVENT_IDS.KILL_PLAYER) .stateChange(game, { personId: 'a' }, { gameManager: gameManager }); const person = game.people.find(p => p.id === 'a'); expect(person.userType).toEqual(USER_TYPES.MODERATOR); expect(person.out).toBeTrue(); expect(person.killed).toBeFalse(); }); }); describe('communicate', () => { it('should communicate the killed player to the room', async () => { await Events.find((e) => e.id === EVENT_IDS.KILL_PLAYER) .communicate(game, { personId: 'b' }, { gameManager: gameManager }); expect(namespace.in).toHaveBeenCalledWith(game.accessCode); expect(namespace.in().emit).toHaveBeenCalledWith( EVENT_IDS.KILL_PLAYER, game.people.find(p => p.id === 'b') ); }); }); }); describe(EVENT_IDS.REVEAL_PLAYER, () => { describe('stateChange', () => { it('should reveal the indicated player', async () => { await Events.find((e) => e.id === EVENT_IDS.REVEAL_PLAYER) .stateChange(game, { personId: 'b' }, { gameManager: gameManager }); const person = game.people.find(p => p.id === 'b'); expect(person.userType).toEqual(USER_TYPES.PLAYER); expect(person.out).toBeFalse(); expect(person.killed).toBeFalse(); expect(person.revealed).toBeTrue(); }); }); describe('communicate', () => { it('should communicate the killed player to the room', async () => { await Events.find((e) => e.id === EVENT_IDS.REVEAL_PLAYER) .communicate(game, { personId: 'b' }, { gameManager: gameManager }); expect(namespace.in).toHaveBeenCalledWith(game.accessCode); expect(namespace.in().emit).toHaveBeenCalledWith( EVENT_IDS.REVEAL_PLAYER, { id: 'b', gameRole: 'Villager', alignment: 'good' } ); }); }); }); describe(EVENT_IDS.END_GAME, () => { describe('stateChange', () => { it('should end the game and reveal all players', async () => { await Events.find((e) => e.id === EVENT_IDS.END_GAME) .stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager }); expect(game.status).toEqual(STATUS.ENDED); expect(game.people.find(p => p.id === 'b').revealed).toBeTrue(); }); it('should end the game and kill the associated timer thread', async () => { game.hasTimer = true; timerManager.timerThreads = { ABCD: { kill: () => {} } }; spyOn(timerManager.timerThreads.ABCD, 'kill').and.callThrough(); await Events.find((e) => e.id === EVENT_IDS.END_GAME) .stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager, timerManager: timerManager, logger: { trace: () => {} } }); expect(game.status).toEqual(STATUS.ENDED); expect(game.people.find(p => p.id === 'b').revealed).toBeTrue(); expect(timerManager.timerThreads.ABCD.kill).toHaveBeenCalled(); }); }); describe('communicate', () => { it('should communicate the end event to the room', async () => { await Events.find((e) => e.id === EVENT_IDS.END_GAME) .communicate(game, {}, { gameManager: gameManager }); expect(namespace.in).toHaveBeenCalledWith(game.accessCode); expect(namespace.in().emit).toHaveBeenCalledWith(EVENT_IDS.END_GAME, GameStateCurator.mapPeopleForModerator(game.people)); }); it('should communicate the end event to the room and acknowledge the client', async () => { const vars = { ackFn: () => {}, gameManager: gameManager }; spyOn(vars, 'ackFn').and.callThrough(); await Events.find((e) => e.id === EVENT_IDS.END_GAME) .communicate(game, {}, vars); expect(namespace.in).toHaveBeenCalledWith(game.accessCode); expect(namespace.in().emit).toHaveBeenCalledWith(EVENT_IDS.END_GAME, GameStateCurator.mapPeopleForModerator(game.people)); expect(vars.ackFn).toHaveBeenCalled(); }); }); }); describe(EVENT_IDS.TRANSFER_MODERATOR, () => { describe('stateChange', () => { it('should transfer from a dedicated mod to a spectator', async () => { await Events.find((e) => e.id === EVENT_IDS.TRANSFER_MODERATOR) .stateChange(game, { personId: 'c' }, { gameManager: gameManager }); expect(game.currentModeratorId).toEqual('c'); expect(game.previousModeratorId).toEqual('a'); expect(game.people.find(p => p.id === 'a').userType).toEqual(USER_TYPES.SPECTATOR); expect(game.people.find(p => p.id === 'c').userType).toEqual(USER_TYPES.MODERATOR); }); it('should transfer from a dedicated mod to a killed player', async () => { await Events.find((e) => e.id === EVENT_IDS.KILL_PLAYER) .stateChange(game, { personId: 'b' }, { gameManager: gameManager }); await Events.find((e) => e.id === EVENT_IDS.TRANSFER_MODERATOR) .stateChange(game, { personId: 'b' }, { gameManager: gameManager }); expect(game.currentModeratorId).toEqual('b'); expect(game.previousModeratorId).toEqual('a'); expect(game.people.find(p => p.id === 'a').userType).toEqual(USER_TYPES.SPECTATOR); expect(game.people.find(p => p.id === 'b').userType).toEqual(USER_TYPES.MODERATOR); }); it('should transfer from a dedicated mod who was a former player, restoring their status to "killed player"', async () => { await Events.find((e) => e.id === EVENT_IDS.KILL_PLAYER) .stateChange(game, { personId: 'b' }, { gameManager: gameManager }); await Events.find((e) => e.id === EVENT_IDS.TRANSFER_MODERATOR) .stateChange(game, { personId: 'b' }, { gameManager: gameManager }); await Events.find((e) => e.id === EVENT_IDS.TRANSFER_MODERATOR) .stateChange(game, { personId: 'a' }, { gameManager: gameManager }); expect(game.currentModeratorId).toEqual('a'); expect(game.previousModeratorId).toEqual('b'); expect(game.people.find(p => p.id === 'a').userType).toEqual(USER_TYPES.MODERATOR); expect(game.people.find(p => p.id === 'b').userType).toEqual(USER_TYPES.KILLED_PLAYER); }); }); describe('communicate', () => { it('should communicate the transfer moderator event to the room', async () => { await Events.find((e) => e.id === EVENT_IDS.TRANSFER_MODERATOR) .communicate(game, {}, { gameManager: gameManager }); expect(namespace.to).toHaveBeenCalledWith(game.accessCode); expect(namespace.to().emit).toHaveBeenCalledWith(EVENT_IDS.SYNC_GAME_STATE); }); it('should communicate the transfer moderator to the room and acknowledge the client', async () => { const vars = { ackFn: () => {}, gameManager: gameManager }; spyOn(vars, 'ackFn').and.callThrough(); await Events.find((e) => e.id === EVENT_IDS.TRANSFER_MODERATOR) .communicate(game, {}, vars); expect(namespace.to).toHaveBeenCalledWith(game.accessCode); expect(namespace.to().emit).toHaveBeenCalledWith(EVENT_IDS.SYNC_GAME_STATE); expect(vars.ackFn).toHaveBeenCalled(); }); }); }); describe(EVENT_IDS.ASSIGN_DEDICATED_MOD, () => { beforeEach(() => { game.people = [ { id: 'a', gameRole: 'Villager', alignment: 'good', assigned: true, out: false, killed: false, userType: USER_TYPES.TEMPORARY_MODERATOR }, { id: 'b', gameRole: 'Villager', alignment: 'good', assigned: true, out: false, killed: false, userType: USER_TYPES.PLAYER }, { id: 'c', assigned: true, out: true, killed: false, userType: USER_TYPES.SPECTATOR } ]; }); describe('stateChange', () => { it('should assign a dedicated mod who is different from the requesting temp mod', async () => { await Events.find((e) => e.id === EVENT_IDS.ASSIGN_DEDICATED_MOD) .stateChange(game, { personId: 'b' }, { gameManager: gameManager }); expect(game.currentModeratorId).toEqual('b'); expect(game.previousModeratorId).toEqual('a'); expect(game.people.find(p => p.id === 'a').userType).toEqual(USER_TYPES.PLAYER); expect(game.people.find(p => p.id === 'b').userType).toEqual(USER_TYPES.MODERATOR); }); it('should assign a dedicated mod who is the same as the requesting temp mod', async () => { await Events.find((e) => e.id === EVENT_IDS.ASSIGN_DEDICATED_MOD) .stateChange(game, { personId: 'a' }, { gameManager: gameManager }); expect(game.currentModeratorId).toEqual('a'); expect(game.previousModeratorId).toEqual('a'); expect(game.people.find(p => p.id === 'a').userType).toEqual(USER_TYPES.MODERATOR); expect(game.people.find(p => p.id === 'b').userType).toEqual(USER_TYPES.PLAYER); }); }); describe('communicate', () => { it('should communicate to the new mod and the previous mod', async () => { game.currentModeratorId = 'a'; game.previousModeratorId = 'b'; game.people.find(p => p.id === 'a').socketId = 'aaa'; game.people.find(p => p.id === 'b').socketId = 'bbb'; const socketToObj = { emit: () => {} }; const mockSocket = { join: () => {}, to: () => { return socketToObj; } }; spyOn(mockSocket, 'to').and.callThrough(); spyOn(socketToObj, 'emit').and.callThrough(); namespace.sockets.set('aaa', mockSocket); namespace.sockets.set('bbb', mockSocket); await Events.find((e) => e.id === EVENT_IDS.ASSIGN_DEDICATED_MOD) .communicate(game, { personId: 'cookie' }, { gameManager: gameManager }); // verify the current mod's view is synced expect(namespace.to).toHaveBeenCalledWith('aaa'); expect(namespace.to().emit).toHaveBeenCalledWith(EVENT_IDS.SYNC_GAME_STATE); // verify the old mod's view is synced expect(namespace.to).toHaveBeenCalledWith('bbb'); expect(namespace.to().emit).toHaveBeenCalledWith(EVENT_IDS.SYNC_GAME_STATE); // verify the "kill player" event is sent to everyone but the sender expect(mockSocket.to).toHaveBeenCalledWith(game.accessCode); expect(socketToObj.emit).toHaveBeenCalledWith(EVENT_IDS.KILL_PLAYER, game.people.find(p => p.id === 'a')); }); it('should not communicate to the current or previous mod if their sockets are not found', async () => { game.currentModeratorId = 'a'; game.previousModeratorId = 'b'; game.people.find(p => p.id === 'a').socketId = 'aaa'; game.people.find(p => p.id === 'b').socketId = 'bbb'; const socketToObj = { emit: () => {} }; const mockSocket = { join: () => {}, to: () => { return socketToObj; } }; spyOn(mockSocket, 'to').and.callThrough(); spyOn(socketToObj, 'emit').and.callThrough(); namespace.sockets.set('yyy', mockSocket); namespace.sockets.set('zzz', mockSocket); await Events.find((e) => e.id === EVENT_IDS.ASSIGN_DEDICATED_MOD) .communicate(game, { personId: 'cookie' }, { gameManager: gameManager }); // verify there's no attempted communication to the new mod (they are not connected to this instance) expect(namespace.to).not.toHaveBeenCalledWith('aaa'); expect(namespace.to().emit).not.toHaveBeenCalled(); // verify there's no attempted communication to the old mod expect(namespace.to).not.toHaveBeenCalledWith('bbb'); expect(namespace.to().emit).not.toHaveBeenCalled(); // verify the "kill player" event is sent to everyone in the room (as opposed to everyone but the sender) expect(namespace.in).toHaveBeenCalledWith(game.accessCode); expect(namespace.in().emit).toHaveBeenCalledWith(EVENT_IDS.KILL_PLAYER, game.people.find(p => p.id === 'a')); }); }); }); describe(EVENT_IDS.RESTART_GAME, () => { describe('stateChange', () => { it('should kill any alive timer thread if the instance is home to it', async () => { const mockThread = { kill: () => {}, killed: false }; timerManager.timerThreads = { ABCD: mockThread }; spyOn(timerManager.timerThreads.ABCD, 'kill').and.callThrough(); await Events.find((e) => e.id === EVENT_IDS.RESTART_GAME) .stateChange(game, { personId: 'b' }, { gameManager: gameManager, timerManager: timerManager, instanceId: '111', senderInstanceId: '222' }); expect(mockThread.kill).toHaveBeenCalled(); expect(Object.keys(timerManager.timerThreads).length).toEqual(0); }); it('should not kill the timer thread if the instance sent the event', async () => { const mockThread = { kill: () => {}, killed: false }; timerManager.timerThreads = { ABCD: mockThread }; spyOn(timerManager.timerThreads.ABCD, 'kill').and.callThrough(); await Events.find((e) => e.id === EVENT_IDS.RESTART_GAME) .stateChange(game, { personId: 'b' }, { gameManager: gameManager, timerManager: timerManager, instanceId: '111', senderInstanceId: '111' }); expect(mockThread.kill).not.toHaveBeenCalled(); expect(Object.keys(timerManager.timerThreads).length).toEqual(1); }); }); describe('communicate', () => { it('should communicate the restart event to the room', async () => { await Events.find((e) => e.id === EVENT_IDS.RESTART_GAME) .communicate(game, {}, { gameManager: gameManager }); expect(namespace.in).toHaveBeenCalledWith(game.accessCode); expect(namespace.in().emit).toHaveBeenCalledWith(EVENT_IDS.RESTART_GAME); }); it('should communicate the restart event to the room and acknowledge the client', async () => { const vars = { ackFn: () => {}, gameManager: gameManager }; spyOn(vars, 'ackFn').and.callThrough(); await Events.find((e) => e.id === EVENT_IDS.RESTART_GAME) .communicate(game, {}, vars); expect(namespace.in).toHaveBeenCalledWith(game.accessCode); expect(namespace.in().emit).toHaveBeenCalledWith(EVENT_IDS.RESTART_GAME); expect(vars.ackFn).toHaveBeenCalled(); }); }); }); describe(EVENT_IDS.TIMER_EVENT, () => { describe('communicate', () => { it('should publish an event to source timer data if the timer thread is not found', async () => { await Events.find((e) => e.id === EVENT_IDS.TIMER_EVENT) .communicate(game, {}, { gameManager: gameManager, timerManager: timerManager, eventManager: eventManager }); expect(eventManager.publisher.publish).toHaveBeenCalled(); }); it('should send a message to the thread if it is found', async () => { const mockThread = { exitCode: null, kill: () => {}, send: (...args) => {}, killed: false }; timerManager.timerThreads = { ABCD: mockThread }; spyOn(timerManager.timerThreads.ABCD, 'send').and.callThrough(); await Events.find((e) => e.id === EVENT_IDS.TIMER_EVENT) .communicate(game, {}, { gameManager: gameManager, timerManager: timerManager, eventManager: eventManager, timerEventSubtype: EVENT_IDS.GET_TIME_REMAINING, requestingSocketId: '2', logger: { logLevel: 'trace' } }); expect(mockThread.send).toHaveBeenCalledWith({ command: EVENT_IDS.GET_TIME_REMAINING, accessCode: 'ABCD', socketId: '2', logLevel: 'trace' }); }); }); }); });