check:poster-design-catalogue: poison a sample that exists but cannot be drawn

The samples arm is now a function the self-test can poison: with withholding
switched off, India (no sheet tier, no outlines) must fail as a sample that
cannot be drawn. Closes the branch the arm had no poison for.
This commit is contained in:
Doxa Maps 2026-10-02 12:20:40 +00:00
parent 2f4cabf85f
commit 6ccbe827bc
2 changed files with 17 additions and 7 deletions

View file

@ -609,7 +609,7 @@
"check:poster-card-fits": "a poster card's print-options row must stay inside its own card. Reported by the repo agent off a 1440px screenshot: 27/27 cards overflowing by 110px, 36 neighbour intrusions. .pfs already had width:82%, so SOURCE READING SAID IT WAS FINE — flex items default to min-width:auto and refuse to shrink. Asserts BOTH no-overlap AND that the controls keep a usable box, because a row whose buttons collapsed to nothing would also \"not overflow\". Sweeps 390/768/1024/1440/1920px and asserts the row sits BELOW the preview too — measuring only left/right edges is how it passed a driver-visible regression. CARD_FIT_WIDTH overrides the list. SKIPs when nothing is served.",
"check:poster-degrades": "with NO stored poster, NO source and NO generator (forced in the browser: every posters/*.webp and *.html answers 404, lib/poster-render.js is aborted), nothing must print and the reader must be told why. Measures any served tree; NOT MEASURED without a wall url.",
"check:poster-design-catalogue": "Poster Design's configuration catalogue (plugin-designs/poster-design/src/templates.js) says only true things about the library: its sheet formats are exactly poster-render.js FORMATS at the same pixel sizes (the generator is a pinned asset, so the table cannot be imported); every configuration name is unique and well formed; the card entries are exactly card-render STOCK; every catalogue key it names exists in en/posterDesign.json; the sample country can be drawn. Six mutants (px drift, a missing format, a duplicate name, a bad key, an undrawable sample, a dropped stock) each FAIL. It also validates every plugin's posterDesign block (plugin-designs/<plugin>/<profile>.meta.json: family, name, size, mobileFriendly, optional labelKey and instance), keeps names unique across templates.js and all blocks, and fails when a name in the names ledger (src/catalogue-names.json) was renamed or removed.",
"check:poster-design-catalogue:self-test": "an invalid posterDesign block, a name that clashes with templates.js, a renamed ledger name and a block naming a preset that does not exist must each make check:poster-design-catalogue fail, and the documented example block must pass.",
"check:poster-design-catalogue:self-test": "an invalid posterDesign block, a name that clashes with templates.js, a renamed ledger name a block naming a preset that does not exist, and a sample that exists but cannot be drawn (India with withholding off) must each make check:poster-design-catalogue fail, and the documented example block must pass.",
"check:poster-fetch-guard": "a host's 200 fallback page is never taken for a poster. On a drop-in dev host a missing posters/<id>.webp answered 200 text/html, and export handed that page to the print dialog as the image. Asserts library/packet/poster-fetch.js accepts a raster only if it is an image (by type or by its first bytes) and a source only if it is a poster document (class=\"stage\"), and that neither usePosterPrints nor PosterWall fetches posters/ directly. Node only, no server.",
"check:poster-layout-optin": "the composed poster sheet (header, map, legend beside it, footer) is OPT-IN: spec.layout === 'poster' asks for it, and every caller that does not gets the full-page map exactly as before, asserted by object identity, not just by value. Drives the real layout decisions with real spec objects. Headless, no browser.",
"check:poster-print-fallback": "clicks the REAL poster print control with the served poster generator and every posters/*.webp aborted, and asserts the preview RASTERISES from posters/<id>.html instead of opening on \"Poster not available yet\". Needs a served tree built with posterSources:true; NOT MEASURED otherwise.",

View file

@ -22,7 +22,8 @@
* (renamed or removed) FAILS: a name is what people hand an agent, so it must keep working (a retired size stays
* as a legacy entry, as templates.js does). A new name passes and is reported, to be added to the ledger.
* A catalogue that lists nothing FAILS (vacuous guard). --self-test: an invalid block, a name clash, a renamed
* ledger name and a block naming a missing preset must each FAIL, and the documented example block must PASS.
* ledger name, a block naming a missing preset, and a sample that exists but cannot be drawn (India with withholding
* off) must each FAIL, and the documented example block must PASS.
*/
import { readFileSync, readdirSync, existsSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
@ -101,12 +102,17 @@ for (const c of configs) if (!cat.FAMILY_KEYS[c.family]) fail(`'${c.name}' has f
// 5. the samples
const data = JSON.parse(readFileSync(join(ROOT, 'library/data/country-posters.json'), 'utf8'))
const samples = [...new Set([cat.SAMPLE_COUNTRY_ID, ...configs.map((c) => c.sample).filter(Boolean)])]
for (const id of samples) {
const s = (data.countries || []).find((c) => c.id === id)
if (!s) fail(`sample '${id}' is not in library/data/country-posters.json`)
else if (sheetWithheld(s)) continue // its card shows the withheld sentence; it is never drawn here
else if (!s.hasGeometry || !s.sheet || s.unsupportedReason) fail(`sample '${s.id}' cannot be drawn (hasGeometry ${s.hasGeometry}, sheet ${s.sheet})`)
function sampleArm(ids, countries, withheld) {
const fs = []
for (const id of ids) {
const s = countries.find((c) => c.id === id)
if (!s) fs.push(`5: sample '${id}' is not in library/data/country-posters.json`)
else if (withheld(s)) continue // its card shows the withheld sentence; it is never drawn here
else if (!s.hasGeometry || !s.sheet || s.unsupportedReason) fs.push(`5: sample '${s.id}' cannot be drawn (hasGeometry ${s.hasGeometry}, sheet ${s.sheet})`)
}
return fs
}
sampleArm(samples, data.countries || [], sheetWithheld).forEach(fail)
// 6-8: plugin designs, unique names, the ledger. Gathered once, judged by one function, so --self-test poisons the
// same function the real run uses.
@ -164,6 +170,10 @@ if (process.argv.includes('--self-test')) {
console.log(hit ? ` ✓ caught: ${name} -> ${hit}` : ` ✗ MISSED: ${name}`)
if (!hit) ok = false
}
// a sample that exists but cannot be drawn: India, once nothing withholds it
const und = sampleArm(samples, data.countries || [], () => false).find((f) => /cannot be drawn/.test(f))
console.log(und ? ` ✓ caught: a sample that exists but cannot be drawn (withholding off) -> ${und}` : ' ✗ MISSED: a sample that exists but cannot be drawn')
if (!und) ok = false
const good = designArms({ ...real, blocks: [...blocks, B(example)] })
console.log(good.fails.length ? ` ✗ the documented example block FAILS: ${good.fails[0]}` : ' ✓ the documented example block passes')
if (good.fails.length) ok = false