import { describe, it, expect, vi, beforeEach } from 'vitest'; import type { LocalGameRecord } from './serialize'; // An in-memory stand-in for the IndexedDB store, so list() — which reads the *persisted* records, // not the source's in-memory cache — can be exercised in the node env (the real store is a // best-effort no-op without IndexedDB). Declared before the vi.mock factory that closes over it. const store = new Map(); vi.mock('./store', () => ({ saveLocalGame: async (r: LocalGameRecord) => void store.set(r.id, r), getLocalGame: async (id: string) => store.get(id) ?? null, listLocalGames: async () => [...store.values()], deleteLocalGame: async (id: string) => void store.delete(id), })); vi.mock('../dict', () => ({ getDawg: async () => { const { Dawg } = await import('../dict/dawg'); const { readFileSync } = await import('node:fs'); return new Dawg(new Uint8Array(readFileSync(new URL('../dict/testdata/sample_en.dawg', import.meta.url)))); }, })); import { LocalSource } from './source'; import type { Seat } from './serialize'; const seats: Seat[] = [ { kind: 'human', name: 'You' }, { kind: 'robot', name: 'Robot' }, ]; const opts = (id: string, seed: bigint) => ({ id, variant: 'scrabble_en' as const, dictVersion: 'sample', seed, multipleWords: true, seats, }); beforeEach(() => store.clear()); describe('LocalSource.list', () => { it('is empty when no games are stored', async () => { expect(await new LocalSource().list()).toEqual([]); }); it('returns a lobby GameView for every stored local game', async () => { const src = new LocalSource(); await src.create(opts('local:a', 1n)); await src.create(opts('local:b', 2n)); const list = await src.list(); expect(new Set(list.map((g) => g.id))).toEqual(new Set(['local:a', 'local:b'])); expect(list.every((g) => g.vsAi && g.status === 'active')).toBe(true); }); it('carries the human seat account id into the GameView so the client identifies "you"', async () => { const src = new LocalSource(); await src.create({ ...opts('local:me', 3n), seats: [ { kind: 'human', name: 'You', accountId: 'acct-42' }, { kind: 'robot', name: 'Robot' }, ], }); const [g] = await src.list(); expect(g.seats[0].accountId).toBe('acct-42'); // the human's real id, matched against the session expect(g.seats[1].accountId).not.toBe('acct-42'); // the robot keeps a synthetic id }); it('reconstructs a game persisted by a previous session (not in the source cache)', async () => { // Seed the store via one source, then list from a fresh source whose in-memory cache is empty. await new LocalSource().create(opts('local:old', 7n)); const list = await new LocalSource().list(); expect(list.map((g) => g.id)).toEqual(['local:old']); expect(list[0].variant).toBe('scrabble_en'); }); });