Add client leaving host notification

This commit is contained in:
Joey Yakimowich-Payne 2026-01-19 15:46:17 -07:00
commit 3c54a0f4d9
No known key found for this signature in database
GPG key ID: DDF6AF5B21B407D4
3 changed files with 164 additions and 0 deletions

View file

@ -909,5 +909,150 @@ describe('Kick and Leave Feature Logic', () => {
expect(isValidPlayerLeftMessage({ type: 'PLAYER_LEFT', payload: {} })).toBe(false);
expect(isValidPlayerLeftMessage({ type: 'PLAYER_LEFT' })).toBe(false);
});
it('validates LEAVE message structure', () => {
const isValidLeaveMessage = (message: unknown): boolean => {
if (typeof message !== 'object' || message === null) return false;
const msg = message as { type?: string; payload?: object };
return msg.type === 'LEAVE' && typeof msg.payload === 'object';
};
expect(isValidLeaveMessage({ type: 'LEAVE', payload: {} })).toBe(true);
expect(isValidLeaveMessage({ type: 'LEAVE' })).toBe(false);
expect(isValidLeaveMessage({ type: 'OTHER', payload: {} })).toBe(false);
expect(isValidLeaveMessage(null)).toBe(false);
});
});
describe('host handling LEAVE message', () => {
const processLeaveMessage = (
leavingPlayerId: string,
players: Player[],
presenterId: string | null
): {
updatedPlayers: Player[];
newPresenterId: string | null;
shouldBroadcastPlayerLeft: boolean;
shouldBroadcastPresenterChanged: boolean;
} => {
const updatedPlayers = players.filter(p => p.id !== leavingPlayerId);
const wasPresenter = presenterId === leavingPlayerId;
let newPresenterId = presenterId;
if (wasPresenter) {
const realPlayers = updatedPlayers.filter(p => p.id !== 'host');
newPresenterId = realPlayers.length > 0 ? realPlayers[0].id : null;
}
return {
updatedPlayers,
newPresenterId,
shouldBroadcastPlayerLeft: true,
shouldBroadcastPresenterChanged: wasPresenter,
};
};
it('removes leaving player from players list', () => {
const players = [
createPlayer({ id: 'player-1', name: 'Alice' }),
createPlayer({ id: 'player-2', name: 'Bob' }),
];
const result = processLeaveMessage('player-1', players, null);
expect(result.updatedPlayers).toHaveLength(1);
expect(result.updatedPlayers[0].id).toBe('player-2');
});
it('broadcasts PLAYER_LEFT to remaining players', () => {
const players = [
createPlayer({ id: 'player-1', name: 'Alice' }),
createPlayer({ id: 'player-2', name: 'Bob' }),
];
const result = processLeaveMessage('player-1', players, null);
expect(result.shouldBroadcastPlayerLeft).toBe(true);
});
it('reassigns presenter when leaving player was presenter', () => {
const players = [
createPlayer({ id: 'player-1', name: 'Alice' }),
createPlayer({ id: 'player-2', name: 'Bob' }),
];
const result = processLeaveMessage('player-1', players, 'player-1');
expect(result.newPresenterId).toBe('player-2');
expect(result.shouldBroadcastPresenterChanged).toBe(true);
});
it('preserves presenter when non-presenter leaves', () => {
const players = [
createPlayer({ id: 'player-1', name: 'Alice' }),
createPlayer({ id: 'player-2', name: 'Bob' }),
];
const result = processLeaveMessage('player-2', players, 'player-1');
expect(result.newPresenterId).toBe('player-1');
expect(result.shouldBroadcastPresenterChanged).toBe(false);
});
it('sets presenter to null when last non-host player leaves', () => {
const players = [
createPlayer({ id: 'host', name: 'Host' }),
createPlayer({ id: 'player-1', name: 'Alice' }),
];
const result = processLeaveMessage('player-1', players, 'player-1');
expect(result.newPresenterId).toBeNull();
expect(result.shouldBroadcastPresenterChanged).toBe(true);
});
it('handles leave when player does not exist', () => {
const players = [
createPlayer({ id: 'player-1', name: 'Alice' }),
];
const result = processLeaveMessage('player-999', players, null);
expect(result.updatedPlayers).toHaveLength(1);
expect(result.shouldBroadcastPlayerLeft).toBe(true);
});
it('does not modify original players array', () => {
const players = [
createPlayer({ id: 'player-1', name: 'Alice' }),
createPlayer({ id: 'player-2', name: 'Bob' }),
];
const originalLength = players.length;
processLeaveMessage('player-1', players, null);
expect(players).toHaveLength(originalLength);
});
it('handles multiple sequential leaves', () => {
let players = [
createPlayer({ id: 'player-1', name: 'Alice' }),
createPlayer({ id: 'player-2', name: 'Bob' }),
createPlayer({ id: 'player-3', name: 'Charlie' }),
];
let presenterId: string | null = 'player-1';
const result1 = processLeaveMessage('player-1', players, presenterId);
players = result1.updatedPlayers;
presenterId = result1.newPresenterId;
const result2 = processLeaveMessage('player-2', players, presenterId);
players = result2.updatedPlayers;
presenterId = result2.newPresenterId;
expect(players).toHaveLength(1);
expect(players[0].id).toBe('player-3');
expect(presenterId).toBe('player-3');
});
});
});