import { test } from 'node:test'; import assert from 'node:assert/strict'; import { createMatchController } from '../src/client/match-controller.js'; import { emptyPlayerStats, type ResultSubmission } from '../src/shared/p2p-types.js'; import { ManualClock } from './helpers.js'; function harness() { const clock = new ManualClock(); clock.time = 1000; const recorded: ResultSubmission[] = [], memory = new Map(); const stats = emptyPlayerStats(); const controller = createMatchController({ activeMatch: { matchId: 'local-bot', seq: 1, mode: 'bot' }, gameSessionId: 'session', selfIndex: 0, clock, random: () => 0, storage: { getItem: k => memory.get(k) ?? null, setItem: (k, v) => { memory.set(k, v); }, removeItem: k => { memory.delete(k); } }, reporter: { submit: async data => { recorded.push(data); return { status: 'applied', stats }; } }, }); return { controller, clock, recorded, memory }; } test('local bot runs countdown, turns, result and persistence without Socket.IO', async () => { const h = harness(); const seen: string[] = []; h.controller.subscribe(s => seen.push(s.phase)); try { await h.controller.start(); await h.clock.advance(3000); assert.equal(h.controller.view().phase, 'selecting'); assert.equal(await h.controller.submitAction('blast'), false); assert.equal(await h.controller.submitAction('charge'), true); assert.equal(h.controller.view().phase, 'revealing'); assert.deepEqual(h.controller.view().energy, [1, 1]); await h.clock.advance(1200); assert.equal(await h.controller.submitAction('blast'), true); await Promise.resolve(); assert.equal(h.controller.view().finished?.winnerIndex, 0); assert.equal(h.controller.view().saveStatus, 'saved'); assert.equal(h.recorded.length, 1); assert.equal(h.recorded[0].outcome, 'win'); assert.ok(seen.includes('countdown')); assert.ok(seen.includes('revealing')); } finally { h.controller.destroy(); } assert.equal(h.clock.timers.size, 0); }); test('timeout records a loss once and local leave records forfeit', async () => { const h = harness(); try { await h.controller.start(); await h.clock.advance(7000); assert.equal(h.recorded[0].outcome, 'loss'); assert.equal(h.recorded[0].reason, 'timeout'); } finally { h.controller.destroy(); } const other = harness(); try { await other.controller.start(); await other.controller.leave(); assert.equal(other.recorded[0].reason, 'forfeit'); assert.equal(other.recorded[0].outcome, 'loss'); } finally { other.controller.destroy(); } });