/** * A match row says what was played on it, and the two ways that can go wrong * both end with a player reading a name that is not the match's. * * The row stores a path into the arena image's scenario library, not a title. * Two of those paths never resolve: a match launched before the column existed * carries none, and a match played on a scenario a later image dropped carries * one the catalog no longer holds. Matches outlive images, so the second is not * hypothetical — the Citadel Blitz entry has already been replaced once. * * Deriving a title from the path is the tempting fix and the wrong one: * `1-FirstRun.mms` prettified is a guess, and a guess sits on the row of a * match the player actually remembers. So both cases answer null, and the row * says nothing rather than something. * * The module under test is TypeScript with extensionless imports, so this * bundles it the way profile.test.mjs does. Run with `npm test`. */ import { test } from "node:test"; import assert from "node:assert/strict"; import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { execFile } from "node:child_process"; import { promisify } from "node:util"; const run = promisify(execFile); const web = join(new URL(".", import.meta.url).pathname, ".."); const dir = await mkdtemp(join(tmpdir(), "scenario-catalog-")); const bundle = join(dir, "fixture.mjs"); await run( join(web, "node_modules/.bin/rolldown"), [ join(web, "scripts/scenario-catalog.fixture.ts"), "--format", "esm", "--platform", "node", "--file", bundle, ], { cwd: web }, ); const { nameByPath, scenarioName } = await import(bundle); /** Two real entries from the catalog `matches/scenarios.rs` serves. */ const CATALOG = [ { path: "TrainingScenarios/1-FirstRun.mms", name: "First Run", slots: [] }, { path: "MonteDiablo/MonteDiablo-Four_Players.mms", name: "The Battle of Monte Diablo", slots: [], }, ]; test("a path the catalog holds is named", () => { const names = nameByPath(CATALOG); assert.equal( scenarioName("TrainingScenarios/1-FirstRun.mms", names), "First Run", ); assert.equal( scenarioName("MonteDiablo/MonteDiablo-Four_Players.mms", names), "The Battle of Monte Diablo", ); }); test("a scenario a later image dropped is not renamed from its path", () => { const names = nameByPath(CATALOG); // The shape that would come back from a match played on Citadel Blitz, which // the catalog replaced. Its row still has to render. assert.equal(scenarioName("Citadel/CitadelBlitz.mms", names), null); }); test("a match from before the column has no scenario at all", () => { const names = nameByPath(CATALOG); assert.equal(scenarioName(null, names), null); assert.equal(scenarioName(undefined, names), null); // Not the same as absent, and just as unnameable. assert.equal(scenarioName("", names), null); }); test("an unread catalog names nothing and throws nothing", () => { // What the screen passes when /api/scenarios failed: the rows lose their // names, and the list still renders. const names = nameByPath([]); assert.equal(names.size, 0); assert.equal(scenarioName("TrainingScenarios/1-FirstRun.mms", names), null); }); test.after(() => rm(dir, { recursive: true, force: true }));