/** * The card reference must not drift from the cards. * * `docs/rules/card-reference.md` spent several releases describing the v0.4.5 deck — twelve numbered * trains, "3 / 4 Mail-Express, 3 coaches" — while `content.ts` had train 3 as the Express with two * freight cars and a per-location freight rule. Worse, `content.ts` named that file as "the place * that now carries what the cards say", so the code sent readers to a table its own banner told them * not to trust. Nothing failed, because nothing checked. * * The card tables in `docs/home-deck.md` and `docs/mainline-deck.md` are emitted from the same * exported catalogues the engine instantiates * from, and this re-runs the generator and compares. Change a card face without regenerating and * this goes red — which is the whole point: a document nothing verifies is a document that will be * wrong, and this project's own history is the evidence. */ import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; import { readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; const root = join(dirname(fileURLToPath(import.meta.url)), '..'); const DOCS = ['docs/home-deck.md', 'docs/mainline-deck.md'].map((rel) => join(root, rel)); describe('the deck references carry generated card tables, and they are current', () => { it('matches what the generator emits from content.ts today', () => { const before = DOCS.map((d) => readFileSync(d, 'utf8')); execFileSync(process.execPath, [join(root, 'scripts/build-card-reference.ts')], { cwd: root }); DOCS.forEach((d, i) => { assert.equal( readFileSync(d, 'utf8'), before[i], `${d} is stale — run \`npm run build:cards\` and commit the result`, ); }); }); it('puts every generated block inside a marker pair that exists', () => { /** * The generator throws on a block with nowhere to go, so this guards the other direction: a * marker pair left in a document with no block to fill it would sit there empty and silent. */ for (const d of DOCS) { const text = readFileSync(d, 'utf8'); const begins = [...text.matchAll(//g)].map((m) => m[1]!); const ends = [...text.matchAll(//g)].map((m) => m[1]!); assert.deepEqual(begins, ends, `${d}: card markers are unbalanced`); for (const key of begins) { const body = text.slice( text.indexOf(``) + ``.length, text.indexOf(``), ); assert.match(body, /\|/, `${d}: the "${key}" block has no table in it`); } } }); it('carries the current train catalogue, not the v0.4.5 deck', () => { // The specific drift that went unnoticed for several releases, asserted by name so a future // regeneration against an old content.ts cannot quietly reintroduce it. const md = readFileSync(join(root, 'docs/home-deck.md'), 'utf8'); assert.match(md, /Crack Limited/); assert.match(md, /\| 3 \| Express \|/); assert.ok(!/Mail-Express/.test(md), 'the superseded v0.4.5 train names are back'); assert.ok(!/Manifest Freight/.test(md), 'the superseded v0.4.5 train names are back'); }); it('says the tables are generated, so nobody edits them by hand', () => { for (const d of DOCS) { const md = readFileSync(d, 'utf8'); assert.match(md, /GENERATED from/, `${d} does not say its tables are generated`); assert.match(md, /`npm run build:cards`/, `${d} does not say what regenerates them`); } }); });