65 lines
3.4 KiB
TypeScript
65 lines
3.4 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 { actChoamCharityFunding, advanceChoamCharityFunding,
|
|
createChoamCharityFundingFixture } from './fixture-choam-charity-funding';
|
|
|
|
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 render = (game: Game, actor: string, busy = false) =>
|
|
renderToStaticMarkup(createElement(GameTable, {game:viewGame(game, actor), busy,
|
|
send:async () => { assert.fail('Rendering must not submit an action.'); }, onExit() {}}));
|
|
|
|
/** The starting deal, Storm and first market are genuine public Basic actions;
|
|
* the only chosen positions are the source fixture's unopened starting cards. */
|
|
function firstMarket() {
|
|
const source = createChoamCharityFundingFixture();
|
|
return advanceChoamCharityFunding(structuredClone(source.afterSetup), state =>
|
|
state.turn === 1 && state.phase === 0 && state.decision?.kind === 'choamMarket');
|
|
}
|
|
|
|
void test('the real CHOAM table offers opt-in, then shows a persistent owner-only cancel control until canceled', () => {
|
|
const market = firstMarket();
|
|
assert.deepEqual(market.expansions, ['choam']);
|
|
assert.equal(market.advanced, false);
|
|
assert.equal(market.decision?.player, 'choam');
|
|
const ownerHtml = render(market, 'choam');
|
|
assert.match(ownerHtml, /Finish and skip remaining markets this turn/);
|
|
assert.match(ownerHtml, /other sales and trades and opportunities to receive gifts/);
|
|
assert.doesNotMatch(render(market, 'atreides'), /Finish and skip remaining markets this turn/);
|
|
|
|
let game = actChoamCharityFunding(market, 'choam', {type:'decision', done:true, skipMarketsThisTurn:true});
|
|
assert.equal(game.phase, 1);
|
|
assert.equal(game.choamMarket, null);
|
|
assert.equal(viewGame(game, 'choam').choamMarketSkipActive, true);
|
|
assert.equal(viewGame(game, 'atreides').choamMarketSkipActive, false);
|
|
for (const state of [game, JSON.parse(JSON.stringify(game)) as Game]) {
|
|
const html = render(state, 'choam');
|
|
assert.match(html, /Your remaining markets this turn will be skipped/);
|
|
assert.match(html, /Cancel skipping remaining markets/);
|
|
assert.match(html, /enable it again only from a future market/);
|
|
assert.doesNotMatch(render(state, 'atreides'), /Cancel skipping remaining markets/);
|
|
assert.doesNotMatch(render(state, 'guild'), /Cancel skipping remaining markets/);
|
|
}
|
|
game = actChoamCharityFunding(game, 'choam', {type:'cancelChoamMarketSkip'});
|
|
assert.equal(viewGame(game, 'choam').choamMarketSkipActive, false);
|
|
assert.doesNotMatch(render(game, 'choam'), /Cancel skipping remaining markets/);
|
|
});
|