/** * The scenario choice on the challenge screen. * * The first scenario arrives already checked, and a radio that is already * checked fires no change event — so the screen has to call chooseScenario * for it explicitly, or the slot list never renders and Deploy never * enables. Both halves have to hold together, which is what these check. * * Structural, like the camo picker's checks. There is no DOM under `npm * test`, and neither failure throws at runtime — the screen renders either * way, it just cannot be got past. * * Run with `npm test`. */ import { test } from "node:test"; import assert from "node:assert/strict"; import { readFile } from "node:fs/promises"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; const web = fileURLToPath(new URL("../", import.meta.url)); const source = await readFile(join(web, "src/screens/challenge.ts"), "utf8"); test("the first scenario arrives checked", () => { const radio = source.match(/el\("input", \{[^}]*name: "scenario"[^}]*\}\)/s); assert.ok(radio, "the scenario radio moved or was renamed"); assert.match( radio[0], /checked: index === 0/, "no scenario is preselected, so every player has to click one before " + "they can go anywhere", ); }); test("the checked scenario is also chosen", () => { // The checked radio fires no change event, so without this call the slot // list stays empty and updateNext never runs with a scenario in hand. // `pool` rather than `catalog`: the Mode-filtered list (see // scenario-pool.test.mjs), not the raw API response. assert.match( source, /if \(pool\[0\]\) chooseScenario\(pool\[0\]\)/, "the preselected scenario is never applied, so the screen arrives dead", ); }); test("Deploy doesn't start out disabled", () => { // Deploy starts enabled and updateNext() is what turns it off and on; if it // started out disabled and the catalog fetch failed, nothing would ever // enable it — and the catch path only sets a note, it does not re-enable. const button = source.match(/const next = el\("button", \{[^}]*\}\)/s); assert.ok(button, "the Deploy button moved or was renamed"); assert.doesNotMatch( button[0], /disabled/, "Deploy must be managed by updateNext(), not disabled from the start", ); assert.match( source, /function updateNext\(\)/, "the seat-validity check moved or was renamed", ); }); test("Deploy reads Deploy, not Next", () => { // The button used to read "Next"; a stray leftover here would ship the // old label even though the launch flow moved on. const button = source.match(/const next = el\("button", \{[^}]*\}\)/s); assert.ok(button, "the Deploy button moved or was renamed"); assert.match( button[0], /textContent: "Deploy"/, "the button no longer reads Deploy", ); assert.match( source, /next\.textContent = "Deploying…"/, "the launching state no longer reads Deploying…", ); });