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 } from '../game/engine'; import type { GameView } from '../game/engine'; import { choamKullGame, kullShipmentAttempt } from './fixture-choam-kull'; 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 }; }, }); // Static component imports would resolve Next modules before the Node alias hook. const { ChoamKull } = await import('../components/choam-kull'); const { GameTable } = await import('../components/game-table'); function offerView(owner = 'c'): GameView { const game = choamKullGame(); const offer = applyAction(game, 'e', kullShipmentAttempt(game)); return viewGame(JSON.parse(JSON.stringify(offer)), owner); } function markup(game: GameView, busy = false): string { return renderToStaticMarkup(createElement(ChoamKull, { game, act() {}, busy })); } void test('only the reactor receives physical Kull cost controls; outsiders cannot inspect offer cards', () => { const holder = offerView(); const rival = offerView('h'); for (const player of rival.players) { if (player.id === rival.me) continue; Object.defineProperty(player, 'hand', { get() { throw new Error('Opponent private hand read'); }, }); } const html = markup(holder); assert.match(html, /]*>Decline Kull/); assert.match(html, /]*>Use Kull Wahad[^<]*ix-kull-wahad/); assert.match(html, /Inspect card/); const outsider = markup(rival); assert.doesNotMatch(outsider, / { const holder = offerView(); const busy = markup(holder, true); assert.match(busy, /disabled=""[^>]*>Decline Kull/); assert.match(busy, /disabled=""[^>]*>Use Kull Wahad/); holder.kullReaction!.blocked = 'The physical cost is reserved.'; assert.doesNotMatch(markup(holder), />Use Kull Wahad/); assert.match(markup(holder), /]*>Decline Kull/); holder.kullReaction!.blocked = null; holder.kullReaction!.plays = []; assert.doesNotMatch(markup(holder), />Use Kull Wahad/); assert.match(markup(holder), /]*>Decline Kull/); holder.kullReaction!.canDecline = false; assert.doesNotMatch(markup(holder), /