test(poster): the shared print actions must produce real documents
Some checks are pending
build-gate / build (push) Waiting to run

Gates usePosterPrints.js — the print actions now shared by every poster wall —
by driving them in a browser with a fake records provider, so the assertion is
about the DOCUMENTS rather than any bundle's API being reachable.

★ BOTH DIRECTIONS, because "the button is there" and "the button works" are
different claims and this flight has repeatedly found the first true while the
second was false. With a provider: a real table, correct row count, the prayer
checkbox column present. WITHOUT one: phase "error" naming the missing provider,
not a silent no-op and not a throw.

⚠ IT CAPTURES THE PRINTED HTML INSTEAD OF READING THE IFRAME BACK, and finding
that out cost a wrong first result. openPrintFrame calls window.print() under
autoprint, which BLOCKS in headless Chromium, and the frame self-removes on
afterprint — so reading it back measured a frame that had already gone and
reported 0 rows for a perfectly correct document. Intercepting what the
composable hands to the frame measures what actually gets printed.

Confirmed to fail before being accepted: deleting the checkbox column from the
row template reports "the list print has no prayer checkbox column".
This commit is contained in:
doxa-maps-88655c70 2026-09-24 04:56:05 +00:00
parent 5c192ea0e2
commit 449adfe515
2 changed files with 112 additions and 0 deletions

View file

@ -0,0 +1,111 @@
// check-poster-prints — the SHARED print actions must produce real documents.
//
// Context: the poster wall was generalised into library/components/poster/
// PosterWall.vue and two bundles migrated onto it, but the ORIGINAL 903-line
// my-upg-100-list wall — the one carrying the actual print actions — was left
// behind. So the shared component's packet path was a "coming soon" stub, and
// every print fix in the 100-list was invisible to the three bundles using the
// shared wall. usePosterPrints.js closes that: the ACTIONS are shared, the RECORD
// FETCHING stays per-bundle behind a `records` provider.
//
// ★ THE SEAM IS THE THING TO ASSERT. A wall WITH a provider must produce a real
// document; a wall WITHOUT one must degrade honestly rather than failing at click
// time. Both directions are checked, because "the button is there" and "the button
// works" are different claims — this flight has repeatedly found the first true
// and the second false.
//
// Runs the composable in a browser page (it calls document/DOM), driving it with a
// fake records provider so the assertion is about the DOCUMENTS, not about any
// bundle's API being reachable.
import { chromium } from 'playwright'
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
const REPO = process.env.DOXA_REPO
|| resolve('/home/lk10x/Workspace/eco-dx/root-doxa-maps/wt-packet')
const src = readFileSync(resolve(REPO, 'library/composables/usePosterPrints.js'), 'utf8')
const frame = readFileSync(resolve(REPO, 'library/composables/printInPlace.js'), 'utf8')
const b = await chromium.launch()
const pg = await b.newPage({ viewport: { width: 1000, height: 800 } })
const errs = []
pg.on('pageerror', (e) => errs.push(String(e).slice(0, 140)))
await pg.setContent('<!DOCTYPE html><html><body></body></html>')
const r = await pg.evaluate(async ({ src, frame }) => {
// Shim the two module deps so the composable can be evaluated standalone: a tiny
// `ref`, and printInPlace's real source so the frame mechanism is the real one.
const frameUrl = URL.createObjectURL(new Blob([frame], { type: 'text/javascript' }))
const vueShim = URL.createObjectURL(new Blob([
'export const ref = (v) => ({ value: v })',
], { type: 'text/javascript' }))
const patched = src
.replace("from 'vue'", `from '${vueShim}'`)
.replace("from './printInPlace.js'", `from '${frameUrl}'`)
const modUrl = URL.createObjectURL(new Blob([patched], { type: 'text/javascript' }))
const { usePosterPrints } = await import(modUrl)
const ROWS = [
{ slug: 'a', name: 'Aheu', country_code_label: 'Laos', population: 2700, religion_label: 'Animism' },
{ slug: 'b', name: 'Akha', country_code_label: 'Laos', population: 113000, religion_label: 'Animism' },
]
const out = {}
// ── with a provider ──────────────────────────────────────────────────────
// ⚠ CAPTURE THE HTML, do not read the frame back. openPrintFrame calls
// window.print() under autoprint, which BLOCKS in headless Chromium, and the
// frame self-removes on afterprint — so reading it back measured a frame that had
// already gone and reported 0 rows for a correct document. Intercept the document
// the composable hands to the frame instead: that IS what gets printed.
let printed = ''
const realCreate = document.createElement.bind(document)
document.createElement = (tag) => {
const el = realCreate(tag)
if (tag === 'iframe') {
Object.defineProperty(el, 'contentDocument', {
get: () => ({ open() {}, close() {}, write(h) { printed += h } }),
})
Object.defineProperty(el, 'contentWindow', { get: () => ({ addEventListener() {} }) })
}
return el
}
const withProv = usePosterPrints({ records: async () => ROWS, label: (i) => i.label })
await withProv.printList({ id: 'laos', label: 'Laos' })
await new Promise((r2) => setTimeout(r2, 400))
document.createElement = realCreate
const doc = new DOMParser().parseFromString(printed, 'text/html')
out.listRows = doc ? doc.querySelectorAll('tbody tr').length : 0
out.listHasCheckbox = doc ? /□/.test(doc.body.innerText) : false
out.listPhase = withProv.exportState.value?.phase ?? 'cleared'
// ── without a provider: must FAIL HONESTLY, not throw ────────────────────
const noProv = usePosterPrints({ label: (i) => i.label })
await noProv.printList({ id: 'x', label: 'X' })
out.noProvPhase = noProv.exportState.value?.phase
out.noProvMsg = noProv.exportState.value?.message || ''
return out
}, { src, frame })
await b.close()
const fails = []
if (r.listRows !== 2) fails.push(`the list print rendered ${r.listRows} row(s), expected 2`)
if (!r.listHasCheckbox) fails.push('the list print has no prayer checkbox column')
if (r.noProvPhase !== 'error') {
fails.push(`without a records provider the phase was "${r.noProvPhase}", expected "error" — `
+ 'it must degrade honestly rather than silently doing nothing')
}
if (!/no records provider/i.test(r.noProvMsg)) {
fails.push(`the no-provider message was "${r.noProvMsg}" — it should name the missing provider`)
}
if (errs.length) fails.push(`${errs.length} page error(s): ${errs[0]}`)
console.log(` list print : ${r.listRows} rows, checkbox=${r.listHasCheckbox}`)
console.log(` without provider : phase=${r.noProvPhase} "${r.noProvMsg.slice(0, 60)}"`)
console.log(` page errors : ${errs.length}`)
if (fails.length) {
console.error(`\nPOSTER-PRINTS: FAIL — ${fails.length} problem(s)`)
for (const f of fails) console.error(' - ' + f)
process.exit(1)
}
console.log('\nPOSTER-PRINTS: PASS — shared print actions produce real documents, and degrade honestly without a provider.')

View file

@ -57,6 +57,7 @@
"build:unlocked": "bun run gates:pre && bun run library-index && bun run check:library-index && bun run clean && bun run token && bun run build:bundles && bun run tree && bun run gates:post && bun run gallery",
"check:card-deck": "node internal/check-card-deck.mjs",
"check:poster-raster": "node internal/check-poster-raster.mjs",
"check:poster-prints": "node internal/check-poster-prints.mjs",
"check:provider-switch": "node internal/check-provider-switch.mjs",
"check:mfe-two-tab": "node internal/check-mfe-two-tab.mjs",
"check:share-policy": "node internal/check-share-policy.mjs",