const { EcastShardClient } = require('../../backend/utils/ecast-shard-client'); describe('EcastShardClient', () => { describe('parsePlayersFromHere', () => { test('counts only player roles, excludes host and shard', () => { const here = { '1': { id: 1, roles: { host: {} } }, '2': { id: 2, roles: { player: { name: 'Alice' } } }, '3': { id: 3, roles: { player: { name: 'Bob' } } }, '5': { id: 5, roles: { shard: {} } }, }; const result = EcastShardClient.parsePlayersFromHere(here); expect(result.playerCount).toBe(2); expect(result.playerNames).toEqual(['Alice', 'Bob']); }); test('returns zero for empty here or host-only', () => { const here = { '1': { id: 1, roles: { host: {} } } }; const result = EcastShardClient.parsePlayersFromHere(here); expect(result.playerCount).toBe(0); expect(result.playerNames).toEqual([]); }); test('handles null or undefined here', () => { expect(EcastShardClient.parsePlayersFromHere(null).playerCount).toBe(0); expect(EcastShardClient.parsePlayersFromHere(undefined).playerCount).toBe(0); }); }); describe('parseRoomEntity', () => { test('extracts lobby state from room entity val', () => { const roomVal = { state: 'Lobby', lobbyState: 'CanStart', gameCanStart: true, gameIsStarting: false, gameFinished: false, }; const result = EcastShardClient.parseRoomEntity(roomVal); expect(result.gameState).toBe('Lobby'); expect(result.lobbyState).toBe('CanStart'); expect(result.gameCanStart).toBe(true); expect(result.gameStarted).toBe(false); expect(result.gameFinished).toBe(false); }); test('detects game started from Gameplay state', () => { const roomVal = { state: 'Gameplay', lobbyState: 'Countdown', gameCanStart: true, gameIsStarting: false, gameFinished: false }; const result = EcastShardClient.parseRoomEntity(roomVal); expect(result.gameStarted).toBe(true); }); test('detects game finished', () => { const roomVal = { state: 'Gameplay', lobbyState: '', gameCanStart: true, gameIsStarting: false, gameFinished: true }; const result = EcastShardClient.parseRoomEntity(roomVal); expect(result.gameFinished).toBe(true); }); }); describe('parsePlayerJoinFromTextDescriptions', () => { test('extracts player name from join description', () => { const val = { latestDescriptions: [ { category: 'TEXT_DESCRIPTION_PLAYER_JOINED', text: 'Charlie joined.' } ] }; const result = EcastShardClient.parsePlayerJoinFromTextDescriptions(val); expect(result).toEqual([{ name: 'Charlie', isVIP: false }]); }); test('extracts VIP join', () => { const val = { latestDescriptions: [ { category: 'TEXT_DESCRIPTION_PLAYER_JOINED_VIP', text: 'Alice joined and is the VIP.' } ] }; const result = EcastShardClient.parsePlayerJoinFromTextDescriptions(val); expect(result).toEqual([{ name: 'Alice', isVIP: true }]); }); test('returns empty array for no joins', () => { const val = { latestDescriptions: [] }; expect(EcastShardClient.parsePlayerJoinFromTextDescriptions(val)).toEqual([]); }); test('handles null/undefined val', () => { expect(EcastShardClient.parsePlayerJoinFromTextDescriptions(null)).toEqual([]); expect(EcastShardClient.parsePlayerJoinFromTextDescriptions(undefined)).toEqual([]); }); }); describe('constructor', () => { test('initializes with correct defaults', () => { const client = new EcastShardClient({ sessionId: 1, gameId: 5, roomCode: 'TEST', maxPlayers: 8, onEvent: () => {} }); expect(client.sessionId).toBe(1); expect(client.gameId).toBe(5); expect(client.roomCode).toBe('TEST'); expect(client.maxPlayers).toBe(8); expect(client.playerCount).toBe(0); expect(client.playerNames).toEqual([]); expect(client.gameStarted).toBe(false); expect(client.gameFinished).toBe(false); expect(client.ws).toBeNull(); }); }); describe('handleWelcome', () => { test('parses welcome message and sets internal state', () => { const client = new EcastShardClient({ sessionId: 1, gameId: 5, roomCode: 'TEST', maxPlayers: 8, onEvent: () => {} }); client.handleWelcome({ id: 7, secret: 'abc-123', reconnect: false, entities: { room: ['object', { key: 'room', val: { state: 'Lobby', lobbyState: 'CanStart', gameCanStart: true, gameIsStarting: false, gameFinished: false }, version: 0, from: 1 }, { locked: false }] }, here: { '1': { id: 1, roles: { host: {} } }, '2': { id: 2, roles: { player: { name: 'Alice' } } }, '3': { id: 3, roles: { player: { name: 'Bob' } } }, } }); expect(client.shardId).toBe(7); expect(client.secret).toBe('abc-123'); expect(client.playerCount).toBe(2); expect(client.playerNames).toEqual(['Alice', 'Bob']); expect(client.gameState).toBe('Lobby'); expect(client.lobbyState).toBe('CanStart'); expect(client.gameStarted).toBe(false); }); }); describe('handleEntityUpdate', () => { test('updates room state on room entity update', () => { const client = new EcastShardClient({ sessionId: 1, gameId: 5, roomCode: 'TEST', maxPlayers: 8, onEvent: () => {} }); client.gameState = 'Lobby'; client.lobbyState = 'WaitingForMore'; client.handleEntityUpdate({ key: 'room', val: { state: 'Gameplay', lobbyState: 'Countdown', gameCanStart: true, gameIsStarting: true, gameFinished: false }, version: 5, from: 1 }); expect(client.gameState).toBe('Gameplay'); expect(client.gameStarted).toBe(true); }); test('handles bc:room key as room update', () => { const client = new EcastShardClient({ sessionId: 1, gameId: 5, roomCode: 'TEST', maxPlayers: 8, onEvent: () => {} }); client.handleEntityUpdate({ key: 'bc:room', val: { state: 'Lobby', lobbyState: 'CanStart', gameCanStart: true, gameIsStarting: false, gameFinished: false }, version: 1, from: 1 }); expect(client.lobbyState).toBe('CanStart'); }); }); });