Expose Advanced printed Kull preview in lobbies
This commit is contained in:
1 parent
f1bef950e0
commit
b750a3d3eb
17 files changed
+422
-20
No files matched your search
@@ -0,0 +1,87 @@
|
||||
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('ADVKULLUI', newPlayer('choam', 'CHOAM', 'choam'), true, ['choam', 'ix']);
|
||||
joinGame(game, newPlayer('emperor', 'Emperor', 'emperor'));
|
||||
return game;
|
||||
}
|
||||
const table = (game: GameView, busy = false) => renderToStaticMarkup(createElement(GameTable,
|
||||
{ game, busy, send: async () => {}, onExit() {} }));
|
||||
|
||||
/** The precise checkbox's ownership state, rather than any other lobby control. */
|
||||
function kullLabel(html: string): string {
|
||||
const label = html.match(/<label\b[^>]*>(?:(?!<\/label>)[\s\S])*Kull Wahad(?:(?!<\/label>)[\s\S])*<\/label>/)?.[0];
|
||||
assert.ok(label, 'The Kull preview choice is present in the lobby');
|
||||
return label;
|
||||
}
|
||||
|
||||
void test('Advanced Kull is off until the host selects the preview and displays its scope and limits', () => {
|
||||
const fresh = lobby(), host = viewGame(fresh, 'choam');
|
||||
assert.equal(host.lobbyModules?.kullPreview, false);
|
||||
assert.equal(host.lobbyModules?.kullDefault, false);
|
||||
const unchecked = table(host);
|
||||
assert.match(kullLabel(unchecked), /type="checkbox"/);
|
||||
assert.match(unchecked, /Kull Wahad ·/);
|
||||
assert.match(unchecked, /Advanced.*development preview/);
|
||||
assert.doesNotMatch(kullLabel(unchecked), /\bchecked=/);
|
||||
assert.doesNotMatch(kullLabel(unchecked), /\bdisabled=/);
|
||||
assert.match(unchecked, /Printed Kull is inactive in this lobby/);
|
||||
|
||||
const selected = applyAction(fresh, 'choam', { type: 'moduleOption', module: 'kullPreview', enabled: true });
|
||||
const selectedView = viewGame(selected, 'choam'), selectedHtml = table(selectedView);
|
||||
assert.equal(selectedView.lobbyModules?.kullPreview, true);
|
||||
assert.match(kullLabel(selectedHtml), /\bchecked=/);
|
||||
assert.match(selectedHtml, /Requires Advanced CHOAM with only classic factions/);
|
||||
assert.match(selectedHtml, /prevents the attempted specific Karama effect/);
|
||||
assert.match(selectedHtml, /phase duration is provisional/);
|
||||
assert.match(selectedHtml, /Structured bid, alliance, Revival, bribe and combined turn-action questions are unavailable/);
|
||||
assert.match(selectedHtml, /Unfunded non-CHOAM Karama-dependent bids remain deferred/);
|
||||
assert.doesNotMatch(selectedHtml, /Printed Kull is inactive in this lobby/);
|
||||
});
|
||||
|
||||
void test('only the host can change the actual Advanced Kull checkbox', () => {
|
||||
const selected = applyAction(lobby(), 'choam', { type: 'moduleOption', module: 'kullPreview', enabled: true });
|
||||
const host = kullLabel(table(viewGame(selected, 'choam')));
|
||||
const guest = kullLabel(table(viewGame(selected, 'emperor')));
|
||||
assert.doesNotMatch(host, /\bdisabled=/);
|
||||
assert.match(guest, /\bdisabled=/);
|
||||
assert.match(kullLabel(table(viewGame(selected, 'choam'), true)), /\bdisabled=/);
|
||||
assert.throws(() => applyAction(selected, 'emperor', { type: 'moduleOption', module: 'kullPreview', enabled: false }));
|
||||
});
|
||||
|
||||
void test('a real Advanced Kull start retains both the Advanced and Kull preview notices', () => {
|
||||
let game = applyAction(lobby(), 'choam', { type: 'moduleOption', module: 'kullPreview', enabled: true });
|
||||
for (const seat of game.players) game = applyAction(game, seat.id, { type: 'ready' });
|
||||
game = applyAction(game, 'choam', { type: 'start', advancedPreview: true });
|
||||
assert.equal(game.status, 'setup');
|
||||
assert.equal(game.kullPreview, true);
|
||||
for (const actor of ['choam', 'emperor']) {
|
||||
const html = table(viewGame(game, actor));
|
||||
assert.match(html, /Advanced preview/);
|
||||
assert.match(html, /Rules and card interactions are still/);
|
||||
assert.match(html, /Kull Wahad development preview/);
|
||||
assert.match(html, /provisional interception timing/);
|
||||
assert.match(html, /not\s+complete CHOAM or certified expansion rules/);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { createGame, joinGame, newPlayer, viewGame, type Action, type Game } from '../game/engine';
|
||||
import { botActions } from '../game/bots';
|
||||
import { act, advance, clean, player, inventory, createAdvancedKullLobbyFixture as fixture,
|
||||
advancedKullAttempt as attempt } from './fixture-advanced-kull-lobby';
|
||||
|
||||
function rejected(game: Game, actor: string, action: Action) {
|
||||
const before = structuredClone(game);
|
||||
assert.throws(() => act(game, actor, action));
|
||||
assert.deepEqual(game, before);
|
||||
}
|
||||
function ready(game: Game): Game {
|
||||
for (const seat of game.players) game = act(game, seat.id, { type: 'ready' });
|
||||
return game;
|
||||
}
|
||||
function lobby(roster: readonly ('atreides' | 'beneGesserit' | 'emperor' | 'fremen' | 'guild' | 'harkonnen')[],
|
||||
advanced = true, expansions = ['choam', 'ix']): Game {
|
||||
const game = createGame('ADVKULL', newPlayer('choam', 'CHOAM', 'choam'), advanced, expansions);
|
||||
for (const faction of roster) joinGame(game, newPlayer(faction, faction, faction));
|
||||
return game;
|
||||
}
|
||||
|
||||
void test('a normal Advanced lobby requires the explicit host selection and preview-start acknowledgement', () => {
|
||||
let game = lobby(['emperor', 'guild']);
|
||||
assert.equal(viewGame(game, 'choam').lobbyModules?.kullPreview, false);
|
||||
rejected(game, 'emperor', { type: 'moduleOption', module: 'kullPreview', enabled: true });
|
||||
game = act(game, 'choam', { type: 'moduleOption', module: 'kullPreview', enabled: true });
|
||||
assert.equal(viewGame(game, 'choam').lobbyModules?.kullPreview, true);
|
||||
game = ready(game);
|
||||
rejected(game, 'choam', { type: 'start' });
|
||||
const started = act(game, 'choam', { type: 'start', advancedPreview: true });
|
||||
assert.equal(started.status, 'setup');
|
||||
assert.equal(started.advanced, true);
|
||||
assert.equal(started.kullPreview, true);
|
||||
assert.equal(viewGame(started, 'choam').kullPreview, true);
|
||||
assert.equal(started.deck.filter(card => card.id === 'ix-kull-wahad').length, 1);
|
||||
});
|
||||
|
||||
for (const seats of [2, 3, 4, 5, 6] as const) {
|
||||
void test(`Advanced Kull public preview starts its native CHOAM and ${seats - 1} classic-seat roster`, () => {
|
||||
const others = ['atreides', 'beneGesserit', 'emperor', 'fremen', 'guild'] as const;
|
||||
let game = lobby(others.slice(0, seats - 1));
|
||||
game = act(game, 'choam', { type: 'moduleOption', module: 'kullPreview', enabled: true });
|
||||
game = act(ready(game), 'choam', { type: 'start', advancedPreview: true });
|
||||
assert.equal(game.status, 'setup');
|
||||
assert.equal(game.players.length, seats);
|
||||
assert.equal(game.kullPreview, true);
|
||||
assert.equal(game.deck.filter(card => card.id === 'ix-kull-wahad').length, 1);
|
||||
});
|
||||
}
|
||||
|
||||
void test('unselected and saved pre-selection Advanced lobbies do not acquire Kull automatically', () => {
|
||||
for (const old of [false, true]) {
|
||||
const game = lobby(['emperor']);
|
||||
if (old) delete game.kullDefaultPolicy;
|
||||
const started = act(ready(game), 'choam', { type: 'start', advancedPreview: true });
|
||||
assert.equal(started.kullPreview ?? false, false);
|
||||
assert.equal(viewGame(started, 'choam').kullPreview, false);
|
||||
}
|
||||
});
|
||||
|
||||
void test('Advanced Kull selection rejects unsupported decks, native factions and modules atomically', () => {
|
||||
for (const kind of ['missingIx', 'richese', 'nexus', 'skills'] as const) {
|
||||
let game = lobby(['emperor'], true, kind === 'missingIx' ? ['choam'] : ['choam', 'ix']);
|
||||
if (kind === 'richese') joinGame(game, newPlayer('richese', 'Richese', 'richese'));
|
||||
if (kind === 'nexus') game = act(game, 'choam', { type: 'moduleOption', module: 'nexusCards', enabled: true });
|
||||
if (kind === 'skills') game = act(game, 'choam', { type: 'moduleOption', module: 'leaderSkills', enabled: true });
|
||||
game = act(game, 'choam', { type: 'moduleOption', module: 'kullPreview', enabled: true });
|
||||
game = ready(game);
|
||||
rejected(game, 'choam', { type: 'start', advancedPreview: true });
|
||||
}
|
||||
});
|
||||
|
||||
void test('normal Advanced Kull preview uses the real held card and distinct counter at the original attempt', () => {
|
||||
const f = fixture();
|
||||
assert.ok(f.actions.some(row => row.actor === f.reactor && row.action.type === 'start' && row.action.advancedPreview === true));
|
||||
const pending = act(f.game, f.actor, attempt(f));
|
||||
assert.equal(pending.pendingKull?.stage, 'offer');
|
||||
assert.ok(player(pending, f.actor).hand.some(card => card.id === f.karama));
|
||||
const reaction = viewGame(pending, f.reactor).kullReaction; assert.ok(reaction);
|
||||
assert.equal(reaction.scope, 'effect');
|
||||
assert.ok(reaction.plays.some(play => play.source === 'printed' && play.card.id === f.kull && !play.blocked));
|
||||
assert.deepEqual(viewGame(pending, f.actor).kullReaction?.plays, []);
|
||||
assert.deepEqual(inventory(pending), f.originalCards);
|
||||
|
||||
const bot = viewGame(pending, f.reactor); bot.players.find(seat => seat.id === f.reactor)!.bot = 'Easy';
|
||||
const botAction = botActions(bot).find(action => action.type === 'kullDecision'); assert.ok(botAction);
|
||||
assert.doesNotThrow(() => act(structuredClone(pending), f.reactor, botAction));
|
||||
|
||||
const declared = act(pending, f.reactor, { type: 'kullDecision', event: reaction.event, source: 'printed', card: f.kull });
|
||||
assert.equal(declared.pendingKull?.stage, 'counter');
|
||||
assert.ok(viewGame(declared, f.counterOwner).responseControls?.cancelCards.includes(f.counter));
|
||||
const countered = act(declared, f.counterOwner, { type: 'card', mode: 'cancel', card: f.counter });
|
||||
const resumed = advance(countered, clean);
|
||||
assert.equal(resumed.karamaShipping?.owner, f.actor);
|
||||
assert.equal(resumed.discard.filter(card => card.id === f.karama).length, 1);
|
||||
assert.equal(resumed.discard.filter(card => card.id === f.counter).length, 1);
|
||||
assert.ok(player(resumed, f.reactor).hand.some(card => card.id === f.kull));
|
||||
assert.deepEqual(inventory(resumed), f.originalCards);
|
||||
|
||||
const successful = advance(declared, clean);
|
||||
assert.equal(successful.karamaShipping, null);
|
||||
assert.ok(player(successful, f.actor).hand.some(card => card.id === f.karama));
|
||||
assert.equal(successful.discard.filter(card => card.id === f.kull).length, 1);
|
||||
assert.equal(viewGame(successful, f.actor).karamaBlocked, null);
|
||||
assert.equal(viewGame(successful, f.actor).karamaPurposeBlocks[0]?.purpose, 'shipment-price');
|
||||
rejected(successful, f.actor, attempt(f));
|
||||
assert.deepEqual(inventory(successful), f.originalCards);
|
||||
});
|
||||
@@ -22,7 +22,7 @@ void test('rules controls identify the unfinished selection before readiness and
|
||||
const preview = render(true, true);
|
||||
assert.match(preview, /value="advanced" selected=""/);
|
||||
assert.match(preview, /Rules and card interactions are still/);
|
||||
assert.match(preview, /six classic factions/);
|
||||
assert.match(preview, /Classic and supported expansion selections are unfinished previews/);
|
||||
assert.match(preview, /Changing rules clears player readiness/);
|
||||
assert.match(preview, /Switching to Basic removes Stronghold Cards/);
|
||||
assert.match(render(true, false), /<select disabled=""/);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { createGame, joinGame, newPlayer, type Action, type Game } from '../game/engine';
|
||||
import { actKullPurpose as act, advanceKullPurpose as advance, kullPurposeClean as clean,
|
||||
kullPurposePlayer as player, type KullPurposeStep } from './fixture-kull-purpose';
|
||||
import { publicKullInventory as inventory } from './fixture-public-choam-kull';
|
||||
|
||||
export { act, advance, clean, player, inventory };
|
||||
|
||||
/** Genuine public Advanced preview entry with an explicit host Kull selection.
|
||||
* The original unopened starting-deal and two Spice positions are selected;
|
||||
* setup, paid shipment and subsequent decisions remain actual native actions. */
|
||||
export function createAdvancedKullLobbyFixture() {
|
||||
let game = createGame('ADVANCEDKULL', newPlayer('choam', 'CHOAM', 'choam'), true, ['choam', 'ix']);
|
||||
for (const faction of ['emperor', 'guild'] as const) joinGame(game, newPlayer(faction, faction, faction));
|
||||
const actions: KullPurposeStep[] = [];
|
||||
const send = (actor: string, action: Action) => { game = act(game, actor, action, actions); };
|
||||
const reach = (until: (g: Game) => boolean) => { game = advance(game, until, actions); };
|
||||
send('choam', { type: 'moduleOption', module: 'kullPreview', enabled: true });
|
||||
for (const seat of game.players) send(seat.id, { type: 'ready' });
|
||||
const lobby = structuredClone(game);
|
||||
send('choam', { type: 'start', advancedPreview: true });
|
||||
const setup = structuredClone(game);
|
||||
assert.equal(game.status, 'setup');
|
||||
assert.equal(game.advanced, true);
|
||||
assert.equal(game.kullPreview, true);
|
||||
assert.ok(game.players.every(seat => seat.hand.length === 0));
|
||||
const originalCards = game.deck.map(card => card.id).sort();
|
||||
const selected: Game['deck'] = [];
|
||||
for (const kind of ['kull', 'karama', 'karama'] as const) {
|
||||
const index = game.deck.findIndex(card => kind === 'kull' ? card.id === 'ix-kull-wahad' : card.effect === 'karama');
|
||||
assert.ok(index >= 0); selected.push(game.deck.splice(index, 1)[0]);
|
||||
}
|
||||
game.deck.unshift(...selected);
|
||||
const beforeDeal = structuredClone(game);
|
||||
reach(state => state.status === 'playing');
|
||||
const afterSetup = structuredClone(game);
|
||||
const [kull, karama, counter] = selected.map(card => card.id);
|
||||
for (const [seat, card] of [['choam', kull], ['emperor', karama], ['guild', counter]] as const)
|
||||
assert.ok(player(game, seat).hand.some(held => held.id === card));
|
||||
assert.deepEqual(inventory(game), originalCards);
|
||||
const safe: Game['spiceDeck'] = [];
|
||||
for (let i = 0; i < 2; i++) {
|
||||
const index = game.spiceDeck.findIndex(card => 'territory' in card && !card.discovery);
|
||||
assert.ok(index >= 0); safe.push(game.spiceDeck.splice(index, 1)[0]);
|
||||
}
|
||||
game.spiceDeck.unshift(...safe);
|
||||
reach(state => state.phase === 5 && state.active === 'choam' && clean(state));
|
||||
send('choam', { type: 'endMovement' });
|
||||
reach(state => state.phase === 5 && state.active === 'emperor' && clean(state));
|
||||
assert.deepEqual(inventory(game), originalCards);
|
||||
return { game, lobby, setup, beforeDeal, afterSetup, actions, originalCards,
|
||||
kull, karama, counter, actor: 'emperor', reactor: 'choam', counterOwner: 'guild' };
|
||||
}
|
||||
|
||||
export type AdvancedKullLobbyFixture = ReturnType<typeof createAdvancedKullLobbyFixture>;
|
||||
export const advancedKullAttempt = (f: AdvancedKullLobbyFixture): Action =>
|
||||
({ type: 'card', mode: 'shipment', card: f.karama, target: f.actor });
|
||||
@@ -0,0 +1,67 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { advancedKullLobbyEligible, advancedKullLobbyProfile } from '../game/kull-advanced-profile';
|
||||
import { kullDefaultEligible, type KullDefaultProfile } from '../game/kull-default-profile';
|
||||
|
||||
const lobby: KullDefaultProfile = {
|
||||
status: 'lobby', advanced: true, expansions: ['choam', 'ix'],
|
||||
players: [{ faction: 'choam' }, { faction: 'atreides' }],
|
||||
};
|
||||
|
||||
void test('Advanced printed Kull supports native CHOAM plus classic factions with the selected decks', () => {
|
||||
assert.deepEqual(advancedKullLobbyProfile(lobby), { eligible: true, reason: null });
|
||||
assert.equal(advancedKullLobbyEligible({ ...lobby, players: [{ faction: 'choam' }] }), true,
|
||||
'The host can see the preview before another seat joins; the start checks its minimum.');
|
||||
for (const faction of ['atreides', 'beneGesserit', 'emperor', 'fremen', 'guild', 'harkonnen'])
|
||||
assert.equal(advancedKullLobbyEligible({ ...lobby, players: [{ faction: 'choam' }, { faction }] }), true, faction);
|
||||
assert.equal(advancedKullLobbyEligible({ ...lobby, expansions: ['ix', 'choam'] }), true);
|
||||
});
|
||||
|
||||
void test('Advanced profile excludes ongoing games, wrong rules, unsupported decks and native expansion seats', () => {
|
||||
for (const profile of [
|
||||
{ ...lobby, status: 'playing' }, { ...lobby, status: 'setup' }, { ...lobby, advanced: false },
|
||||
{ ...lobby, players: [{ faction: 'emperor' }, { faction: 'guild' }] },
|
||||
{ ...lobby, expansions: ['choam'] }, { ...lobby, expansions: ['ix'] },
|
||||
{ ...lobby, expansions: ['choam', 'ix', 'ecaz'] }, { ...lobby, expansions: ['choam', 'choam'] },
|
||||
{ ...lobby, players: [] }, { ...lobby, players: [{ faction: 'choam' }, { faction: 'choam' }] },
|
||||
...['richese', 'ixians', 'tleilaxu', 'ecaz', 'moritani', 'unknown'].map(faction =>
|
||||
({ ...lobby, players: [{ faction: 'choam' }, { faction }] })),
|
||||
{ ...lobby, players: ['choam', 'atreides', 'beneGesserit', 'emperor', 'fremen', 'guild', 'harkonnen']
|
||||
.map(faction => ({ faction })) },
|
||||
]) {
|
||||
const result = advancedKullLobbyProfile(profile);
|
||||
assert.equal(result.eligible, false); assert.ok(result.reason);
|
||||
}
|
||||
});
|
||||
|
||||
void test('Advanced lobby profile excludes all optional modules on source and public projection', () => {
|
||||
for (const option of [
|
||||
{ lobbyOptions: { leaderSkills: true } }, { lobbyOptions: { ecazTreachery: true } },
|
||||
{ lobbyModules: { leaderSkills: true } }, { lobbyModules: { ecazTreachery: true } },
|
||||
{ lobbyModules: { nexusCards: true } }, { lobbyModules: { discoveries: true } },
|
||||
{ leaderSkills: {} }, { ecazTreachery: true }, { nexusCards: {} }, { discoveryEnabled: true },
|
||||
{ discoveries: {} }, { discoveryStash: {} }, { greatMaker: {} },
|
||||
{ homeworlds: {} }, { techTokens: {} }, { strongholdCards: {} },
|
||||
]) assert.equal(advancedKullLobbyEligible({ ...lobby, ...option }), false);
|
||||
assert.equal(advancedKullLobbyEligible({ ...lobby,
|
||||
lobbyModules: { leaderSkills: false, ecazTreachery: false, nexusCards: false, discoveries: false },
|
||||
lobbyOptions: { leaderSkills: false, ecazTreachery: false } }), true);
|
||||
});
|
||||
|
||||
void test('Advanced eligibility does not alter Basic eligibility or depend on a host opt-out', () => {
|
||||
const basic = { ...lobby, advanced: false };
|
||||
const selection = { leaderSkills: false, kullOptOut: true };
|
||||
assert.equal(kullDefaultEligible(basic), true);
|
||||
assert.equal(advancedKullLobbyEligible(basic), false);
|
||||
assert.equal(advancedKullLobbyEligible({ ...lobby, lobbyOptions: selection }), true);
|
||||
});
|
||||
|
||||
void test('Advanced eligibility reads only public faction/configuration and never private hands or spice', () => {
|
||||
const choam = { faction: 'choam', get hand() { throw Error('private hand read'); },
|
||||
get spice() { throw Error('private spice read'); } };
|
||||
const rival = { faction: 'beneGesserit', get hand() { throw Error('private rival hand read'); } };
|
||||
const game = { ...lobby, players: [choam, rival] };
|
||||
assert.equal(advancedKullLobbyEligible(game), true);
|
||||
assert.deepEqual(game.expansions, ['choam', 'ix']);
|
||||
assert.equal(game.status, 'lobby');
|
||||
});
|
||||
@@ -31,15 +31,15 @@ void test('public host lobby shows the explicitly selected Kull preview and its
|
||||
assert.equal(host.lobbyModules?.kullPreview,true);
|
||||
assert.match(html,/Kull Wahad · Basic development preview/);
|
||||
assert.match(html,/Printed Kull is enabled by default/);
|
||||
assert.match(html,/does not support phase-wide bid or combined turn-action Truthtrance questions/);
|
||||
assert.match(html,/defers unfunded non-CHOAM Karama-dependent bids/);
|
||||
assert.match(html,/When Richese is seated, structured bid, alliance, Revival and bribe commitments are unavailable/);
|
||||
assert.match(html,/Phase-wide bid and combined turn-action Truthtrance questions are unavailable/);
|
||||
assert.match(html,/Unfunded non-CHOAM Karama-dependent bids remain deferred/);
|
||||
assert.match(html,/When Richese is seated, structured bid, alliance, Revival and bribe commitments are also unavailable/);
|
||||
const off = act(f.lobby,f.lobby.host,{type:'moduleOption',module:'kullPreview',enabled:false});
|
||||
const unselected = viewGame(off,off.host);
|
||||
assert.equal(unselected.lobbyModules?.kullPreview,false);
|
||||
assert.match(table(unselected),/Kull Wahad · Basic development preview/);
|
||||
assert.match(table(unselected),/The host has turned off the printed Kull preview/);
|
||||
assert.doesNotMatch(table(unselected),/does not support phase-wide bid or combined turn-action Truthtrance questions/);
|
||||
assert.doesNotMatch(table(unselected),/Phase-wide bid and combined turn-action Truthtrance questions are unavailable/);
|
||||
const nonhost = table(viewGame(f.lobby,f.actor));
|
||||
assert.match(nonhost,/disabled=""[^>]*type="checkbox"|type="checkbox"[^>]*disabled=""/);
|
||||
});
|
||||
|
||||
@@ -51,7 +51,7 @@ void test('Kull is a host-configurable Basic lobby preview with supported Truth
|
||||
|
||||
void test('incompatible opt-in starts reject without modifying their original lobby', () => {
|
||||
for (const config of [
|
||||
{advanced:true,expansions:['choam','ix'],last:'guild' as const,module:false},
|
||||
{advanced:true,expansions:['choam','ix'],last:'guild' as const,module:true},
|
||||
{advanced:false,expansions:['choam'],last:'guild' as const,module:false},
|
||||
{advanced:false,expansions:['choam','ix'],last:'guild' as const,module:true},
|
||||
]) {
|
||||
|
||||
Reference in new issue
Block a user