79 lines
3.7 KiB
TypeScript
79 lines
3.7 KiB
TypeScript
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,/<button(?=[^>]*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,/<button(?=[^>]*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);
|
||
});
|