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, createGame, joinGame, newPlayer, viewGame, type Game, type GameView } from '../game/engine'; 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(); function lobby(): Game { const game = createGame('GROUPPREVIEWUI',newPlayer('at','Atreides','atreides')); joinGame(game,newPlayer('em','Emperor','emperor')); joinGame(game,newPlayer('gu','Guild','guild')); return game; } const table = (game: GameView, busy = false) => renderToStaticMarkup(createElement(GameTable, {game,busy,send:async () => {},onExit() {}})); function groupLabel(html: string): string { const label = html.match(/]*>(?:(?!<\/label>)[\s\S])*Group alliances ยท unfinished preview(?:(?!<\/label>)[\s\S])*<\/label>/)?.[0]; assert.ok(label, 'The group-alliance preview selector is visible.'); return label; } void test('host sees each group variant and guests cannot change the selector', () => { const game = lobby(), host = groupLabel(table(viewGame(game,'at'))); assert.match(host,/value="off"/); assert.match(host,/value="three"/); assert.match(host,/value="three-short"/); assert.match(host,/value="unlimited"/); assert.match(host,/Up to three members/); assert.match(host,/Unlimited members/); assert.doesNotMatch(host,/]*disabled/); assert.match(groupLabel(table(viewGame(game,'em'))),/]*disabled/); assert.match(groupLabel(table(viewGame(game,'at'),true)),/]*disabled/); }); void test('selected preview warns in the lobby and through real setup', () => { let game = applyAction(lobby(),'at',{type:'coalitionPreview',variant:'three-short'}); const lobbyHtml = table(viewGame(game,'at')); assert.match(lobbyHtml,/This incomplete preview needs three to six classic factions/); assert.match(lobbyHtml,/Some powers and interactions remain unfinished/); for (const seat of game.players) game = applyAction(game,seat.id,{type:'ready'}); game = applyAction(game,'at',{type:'start'}); for (const actor of ['at','em','gu']) { const html = table(viewGame(game,actor)); assert.match(html,/Group alliances are an unfinished preview/); assert.match(html,/Some faction powers and interactions are incomplete/); } }); void test('Advanced group preview keeps the explicit opt-in and incompatibility warning', () => { let game = applyAction(lobby(),'at',{type:'coalitionPreview',variant:'three'}); game = applyAction(game,'at',{type:'rules',advanced:true}); const html = table(viewGame(game,'at')); assert.match(html,/Advanced rules require their separate preview start/); assert.match(html,/without expansion decks, other optional modules/); assert.match(html,/Begin Advanced preview/); });