import assert from 'node:assert/strict'; import test from 'node:test'; import { registerHooks } from 'node:module'; import { readFileSync } from 'node:fs'; import { createElement } from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import { applyAction, viewGame, type GameView } from '../game/engine'; import { advanceShortAlliance, shortAllianceLobby, type ShortAllianceStep } from './fixture-short-alliance-victory'; const aliases = registerHooks({ resolve(specifier, context, next) { return next(specifier === 'next/image' ? 'vinext/shims/image' : specifier === 'next/link' ? 'vinext/shims/link' : specifier, context); }, load(url, context, next) { if (!url.endsWith('.module.css')) return next(url, context); const css = readFileSync(new URL(url), 'utf8'); const classes = Object.fromEntries([...css.matchAll(/\.([a-zA-Z_][\w-]*)/g)].map(match => [match[1], match[1]])); return { format: 'module', source: `export default ${JSON.stringify(classes)}`, shortCircuit: true }; }, }); const { GameTable } = await import('../components/game-table'); aliases.deregister(); const table = (game: GameView, busy = false) => renderToStaticMarkup(createElement(GameTable, { game, busy, send: async () => {}, onExit() {} })); function allianceLabel(html: string) { const label = html.match(/]*>(?:(?!<\/label>)[\s\S])*Optional: two-member alliances win with three strongholds(?:(?!<\/label>)[\s\S])*<\/label>/)?.[0]; assert.ok(label, 'The optional alliance victory control is present.'); return label; } void test('short alliance lobby option shows its selected state and belongs to the host', () => { const original = shortAllianceLobby(); const fresh = allianceLabel(table(viewGame(original, 'at'))); assert.match(fresh, /type="checkbox"/); assert.doesNotMatch(fresh, /\bchecked=/); assert.doesNotMatch(fresh, /\bdisabled=/); const selected = applyAction(original, 'at', { type: 'shortAllianceVictory', enabled: true }); const host = allianceLabel(table(viewGame(selected, 'at'))); const guest = allianceLabel(table(viewGame(selected, 'em'))); assert.match(host, /\bchecked=/); assert.doesNotMatch(host, /\bdisabled=/); assert.match(guest, /\bchecked=/); assert.match(guest, /\bdisabled=/); assert.match(allianceLabel(table(viewGame(selected, 'at'), true)), /\bdisabled=/); assert.throws(() => applyAction(selected, 'em', { type: 'shortAllianceVictory', enabled: false })); }); void test('a real started game keeps its selected alliance rule visible to every seat', () => { let game = applyAction(shortAllianceLobby(), 'at', { type: 'shortAllianceVictory', enabled: true }); for (const player of game.players) game = applyAction(game, player.id, { type: 'ready' }); game = applyAction(game, 'at', { type: 'start' }); const actions: ShortAllianceStep[] = []; game = advanceShortAlliance(game, state => state.status === 'playing', actions); assert.ok(actions.length > 0, 'Complete the real private setup controls.'); for (const player of game.players) { const html = table(viewGame(game, player.id)); assert.match(html, /Optional alliance rule: a two-member alliance wins with three strongholds/); assert.match(html, /Two-controller games retain their four-stronghold target/); } });