import { beforeEach, describe, expect, it, vi } from 'vitest'; import type { GameView, StateView } from './model'; // Mock the gateway singleton so preload's fan-out is observed without a transport. const mocks = vi.hoisted(() => ({ gameState: vi.fn(), gameHistory: vi.fn(), draftGet: vi.fn(), })); vi.mock('./gateway', () => ({ gateway: { gameState: mocks.gameState, gameHistory: mocks.gameHistory, draftGet: mocks.draftGet }, })); import { preloadGames } from './preload'; import { clearGameCache, getCachedGame, setCachedGame } from './gamecache'; function gameView(id: string, status: GameView['status'] = 'active'): GameView { return { id, variant: 'scrabble_en', dictVersion: 'v1', vsAi: false, unreadChat: false, unreadMessages: false, status, players: 2, toMove: 0, turnTimeoutSecs: 300, multipleWordsPerTurn: true, moveCount: 0, endReason: '', lastActivityUnix: 0, seats: [], }; } function stateView(id: string): StateView { return { game: gameView(id), seat: 0, rack: ['A', 'B'], bagLen: 50, hintsRemaining: 1, walletBalance: 0 }; } beforeEach(() => { clearGameCache(); mocks.gameState.mockReset().mockImplementation((id: string) => Promise.resolve(stateView(id))); mocks.gameHistory.mockReset().mockImplementation((id: string) => Promise.resolve({ gameId: id, moves: [] })); mocks.draftGet.mockReset().mockImplementation((id: string) => Promise.resolve(id === 'g1' ? 'DRAFT1' : '')); }); describe('preloadGames', () => { it('warms ongoing, uncached games with state, history and draft', async () => { await preloadGames([gameView('g1'), gameView('g2')]); expect(getCachedGame('g1')?.view.game.id).toBe('g1'); expect(getCachedGame('g1')?.draft).toBe('DRAFT1'); expect(getCachedGame('g2')?.draft).toBe(''); }); it('skips finished games', async () => { await preloadGames([gameView('done', 'finished')]); expect(getCachedGame('done')).toBeUndefined(); expect(mocks.gameState).not.toHaveBeenCalled(); }); it('skips games already in the cache (kept fresh by the live stream)', async () => { setCachedGame('g1', stateView('g1'), [], 'KEEP'); await preloadGames([gameView('g1'), gameView('g2')]); expect(mocks.gameState).toHaveBeenCalledTimes(1); expect(mocks.gameState).toHaveBeenCalledWith('g2', expect.any(Boolean)); expect(getCachedGame('g1')?.draft).toBe('KEEP'); // untouched }); it('does nothing for an empty list', async () => { await preloadGames([]); expect(mocks.gameState).not.toHaveBeenCalled(); }); });