'use strict'; const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const { YarnGame } = require('../server'); /** * Deterministic timer double so gameplay phases can be advanced in tests * without real waiting. */ function createFakeTimers() { let nextId = 1; const intervals = new Map(); const timeouts = new Map(); return { intervals, timeouts, setInterval(fn, ms) { const id = nextId += 1; intervals.set(id, { fn, ms }); return id; }, clearInterval(id) { intervals.delete(id); }, setTimeout(fn, ms) { const id = nextId += 1; timeouts.set(id, { fn, ms }); return id; }, clearTimeout(id) { timeouts.delete(id); }, /** Fire every live interval `times` times (snapshotting per pass). */ runIntervals(times = 1) { for (let pass = 0; pass < times; pass += 1) { for (const [id, { fn }] of [...intervals.entries()]) { if (!intervals.has(id)) continue; fn(); } } }, /** Fire every pending timeout once. */ runTimeouts() { const pending = [...timeouts.values()]; timeouts.clear(); for (const { fn } of pending) fn(); } }; } function tempStoriesDir() { return fs.mkdtempSync(path.join(os.tmpdir(), 'yarn-test-')); } function makeGame(overrides = {}) { const timers = overrides.timers || createFakeTimers(); const events = []; const game = new YarnGame(overrides.roomName || 'test-room', { timers, broadcast: (event, payload) => events.push({ event, payload }), botFactory: overrides.botFactory, botType: overrides.botType || 'random', storiesDir: overrides.storiesDir || tempStoriesDir() }); const emitted = (name) => events.filter((entry) => entry.event === name); return { game, timers, events, emitted }; } function addPlayers(game, count) { const ids = []; for (let i = 0; i < count; i += 1) { const id = `p${i + 1}`; game.addPlayer(id, `Player ${i + 1}`, `socket-${i + 1}`); ids.push(id); } return ids; } /** Start a game with `count` human players and no bots. */ async function startGame(game, count, settings = {}) { const ids = addPlayers(game, count); await game.startGame({ botCount: 0, timeLimit: 60, roundLimit: 15, ...settings }); return ids; } /** Submit one entry per player, in order. */ function submitAll(game, entries) { entries.forEach(([playerId, text], index) => { const result = game.submitEntry(playerId, text); if (!result.ok) throw new Error(`submit failed for ${playerId}: ${result.error}`); void index; }); } /** Cast the supplied votes and let the game close voting if complete. */ function voteAll(game, votes) { for (const [playerId, entryIndex] of Object.entries(votes)) { const result = game.submitVote(playerId, entryIndex); if (!result.ok) throw new Error(`vote failed for ${playerId}: ${result.error}`); } game.checkVotesComplete(); } module.exports = { createFakeTimers, tempStoriesDir, makeGame, addPlayers, startGame, submitAll, voteAll };