Third instance of one pattern in this flight, after check-zone-census and check-share-policy: a new internal/ gate authored in one worktree hardcodes THAT worktree (/home/.../wt-packet) as its REPO fallback, and check:no-abs-paths -- which runs inside build:unlocked -- turns red for every other builder on the next merge. Same minimal fix each time: DOXA_REPO still wins and is untouched; only the default becomes portable, via the idiom check-no-absolute-paths.mjs prints in its own failure text. Verified both directions: check:no-abs-paths 0, and check:poster-prints itself 0 on the derived default. Three occurrences is a pattern, not bad luck. Whoever is generating these gates should take the portable default as the template, or the next one lands red too.
117 lines
6.2 KiB
JavaScript
117 lines
6.2 KiB
JavaScript
// 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, dirname } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
const REPO = process.env.DOXA_REPO
|
|
// Third instance of one pattern this flight (after check-zone-census and
|
|
// check-share-policy): a gate authored in one worktree hardcodes THAT worktree as its
|
|
// fallback, and check:no-abs-paths — which is inside build:unlocked — turns red for every
|
|
// other builder. DOXA_REPO still wins; only the default is made portable, using the idiom
|
|
// check-no-absolute-paths.mjs prints in its own failure text.
|
|
|| resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
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.')
|