import assert from 'node:assert/strict'; import test from 'node:test'; import { createElement } from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import { VictoryContinuation, VictoryContinuationButton } from '../components/victory-continuation'; import { createGame, joinGame, newPlayer, viewGame, type Action, type GameView } from '../game/engine'; /** Controlled public projections exercise the actual panel and callbacks; * they do not assert an authoritative voting or victory transition. */ function projected(viewer: 'a' | 'b' | 'c', extra: number | null, actor: 'a' | 'b' = 'a'): GameView { const source = createGame('VOTEUI', newPlayer('a', 'Atreides', 'atreides'), true); joinGame(source, newPlayer('b', 'Emperor', 'emperor')); joinGame(source, newPlayer('c', 'Guild', 'guild')); const view = viewGame(source, viewer); view.victoryContinuation = { event: 'public-vote-event', actor, voters: ['a', 'b'], sides: [['a'], ['b', 'c']], extra, maxExtra: 20, }; return view; } function html(game: GameView, busy = false): string { return renderToStaticMarkup(createElement(VictoryContinuation, { game, busy, act() {} })); } function click(mode: 'finish' | 'continue' | 'accept', busy = false, extra?: number): Action[] { const actions: Action[] = []; VictoryContinuationButton({ event: 'public-vote-event', mode, extra, busy, act(action) { actions.push(action); }, children: 'Choose' }).props.onClick(); return actions; } void test('proposal controls display the public sides and a selectable increase', () => { const view = projected('a', null), before = structuredClone(view), output = html(view); assert.match(output, /aria-label="Simultaneous victory decision"/); assert.match(output, /Atreides; Emperor and Guild/); assert.match(output, /unanimously agree to play another turn/); assert.match(output, /Increase the stronghold targets by/); assert.match(output, /type="number" min="0" max="20"/); assert.match(output, /Choose zero to keep the existing targets/); assert.match(output, /Propose another turn/); assert.match(output, /Finish the game/); assert.deepEqual(view, before); }); void test('proposal acceptance explains exact increase and non-actors wait', () => { const responder = html(projected('b', 2, 'b')); assert.match(responder, /Proposal: Continue and increase the stronghold targets by 2/); assert.match(responder, /Agree to continue/); assert.match(responder, /Finish the game/); assert.doesNotMatch(responder, /Propose another turn|type="number"/); const waiting = html(projected('a', 0, 'b')); assert.match(waiting, /Continue with the current stronghold targets/); assert.match(waiting, /Waiting for Emperor to decide/); assert.doesNotMatch(waiting, /