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 { applyAction, viewGame, type Game } from '../game/engine'; import { baseDeck } from '../game/cards'; import { leaderSkillBattle } from './leader-skill-battle-fixture'; import { takeBattleCard, diplomatDefenseGame, revealDiplomatPlans } from './diplomat-defense-fixture'; 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(m => [m[1], m[1]])); return { format: 'module', source: `export default ${JSON.stringify(classes)}`, shortCircuit: true }; } }); const { GameTable } = await import('../components/game-table'); const { RevealedBattle } = await import('../components/revealed-battle'); const { RevealedPlanPieces } = await import('../components/revealed-plan-pieces'); aliases.deregister(); const render = (g: Game, player: string) => renderToStaticMarkup(createElement(RevealedBattle, { game: viewGame(g, player) })); void test('public plan pieces stay absent before both real plans are revealed, including for their owners', () => { let g = leaderSkillBattle({ skill: 'warmaster', unsealed: true, hide: false, advanced: true }); const weapon = takeBattleCard(g, 'd', 'poison'); for (const p of g.players) assert.equal(render(g, p.id), ''); g = applyAction(g, 'd', { type: 'battlePlan', dial: .5, support: 0, leader: 'guild-1', weapon: weapon.id }); assert.ok(!g.battle!.revealed); for (const p of g.players) assert.equal(render(g, p.id), ''); }); void test('actual revealed plans show physical faces in attacker/defender order and never include unplayed cards', () => { let g = leaderSkillBattle({ skill: 'warmaster', unsealed: true, hide: false, advanced: true }); const ownWeapon = takeBattleCard(g, 'a', 'projectile'); const secret = takeBattleCard(g, 'a', 'lasgun'); const otherWeapon = takeBattleCard(g, 'd', 'poison'); const otherDefense = takeBattleCard(g, 'd', 'snooper'); g = applyAction(g, 'd', { type: 'battlePlan', dial: .5, support: 0, leader: 'guild-1', weapon: otherWeapon.id, defense: otherDefense.id }); g = applyAction(g, 'a', { type: 'battlePlan', dial: 1, support: 1, leader: 'emperor-1', weapon: ownWeapon.id }); assert.ok(g.battle!.revealed); const before = JSON.stringify(g); for (const p of g.players) { const html = render(g, p.id); assert.ok(html.indexOf('aria-label="Attacker plan"') < html.indexOf('aria-label="Defender plan"')); assert.equal((html.match(/aria-label="Revealed force dial"/g) ?? []).length, 2); assert.match(html, /