import assert from 'node:assert/strict'; import test from 'node:test'; import { readFileSync } from 'node:fs'; import { registerHooks } from 'node:module'; import { createElement } from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import { viewGame, type Game } from '../game/engine'; import { actAdvisorWormRide as act, advisorWormRidePlayer as player, createAdvisorWormRideFixture as fixture } from './fixture-advisor-worm-ride-flip'; 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(); /** Public setup is genuine; the fixture discloses its staged ride and conserved * force placement. The real ride action opens the response under test. */ function response() { const f = fixture(); const game = act(f.game,f.fremen,f.action); assert.equal(game.response?.automaticAdvisorFlip?.producer,'wormRide'); return {...f,game}; } function table(game:Game, player:string, busy = false) { return renderToStaticMarkup(createElement(GameTable, { game:viewGame(game,player), busy, send:async () => { assert.fail('Rendering must not submit an action.'); }, onExit() { assert.fail('Rendering must not leave the table.'); }, })); } void test('the real worm-ride conversion explains its committed source to every seat', () => { const f = response(), before = structuredClone(f.game); for (const actor of [f.bg,f.fremen,f.holder]) { const html = table(f.game,actor); assert.match(html,/Bene Gesserit token flip/); assert.match(html,/The worm ride and any collection are complete/); assert.match(html,/advisors are now alone and must become fighters before the original arrival continues/); assert.match(html,/Karama prevents only this conversion/); } assert.deepEqual(f.game,before); }); void test('only the eligible holder sees native Karama and allowance controls', () => { const f = response(), holder = viewGame(f.game,f.holder); assert.ok(holder.responseControls?.cancelCards.includes(f.karama)); const html = table(f.game,f.holder); assert.match(html,/Cancel with Karama/); assert.match(html,/Allow this power/); assert.doesNotMatch(table(f.game,f.bg),/Cancel with Karama/); const busy = table(f.game,f.holder,true); assert.match(busy,/]*disabled="")[^>]*>Cancel with Karama/); }); void test('Fremen sees why summoning another worm waits for the current conversion', () => { const f = fixture(); const holder = player(f.game,f.holder), fremen = player(f.game,f.fremen); const index = holder.hand.findIndex(card => card.id === f.karama); assert.ok(index >= 0 && fremen.hand.length > 0); // Conserved physical-card swap before the actual ride gives Fremen Karama. [holder.hand[index],fremen.hand[0]] = [fremen.hand[0],holder.hand[index]]; const game = act(f.game,f.fremen,f.action), before = structuredClone(game); const html = table(game,f.fremen); assert.match(html,/Resolve the completed worm ride’s advisor conversion before summoning another worm/); assert.match(html,/]*disabled="")[^>]*>Spend Karama · summon Shai-Hulud/); assert.throws(() => act(game,f.fremen,{type:'card',mode:'special',card:f.karama,territory:'hagga_basin'})); assert.deepEqual(game,before); });