test(poster): assert the shared PACKET action's shape, not just its bytes
Some checks are pending
build-gate / build (push) Waiting to run
Some checks are pending
build-gate / build (push) Waiting to run
The packet is the product's core artefact, and it moved into the shared composable this tick. The gate now drives printPacket and asserts what came out: 4 sheets for 2 groups (map one-pager + one people page + prayer points + prayer wheel), 12 wheel segments, Mark 5:1-20 present. Confirmed to fail before acceptance — removing the wheel page reports 3 sheets and 0 segments. ⚠ THE HARNESS HAD TO RESOLVE THE RENDERERS, and the first run exposed why. The composable imports them by RELATIVE path; under a blob URL there is no directory to resolve '../packet/...' against, so the action failed with "Failed to resolve module specifier" and printed nothing — reported as 0 sheets, which reads like a product bug. It is not: Vite resolves them and the built bundle inlines both renderers (verified in the shipped my-upg-100-list.js). The harness now substitutes blob URLs for packet-render, card-render, qr and the strings catalog, so the gate exercises the REAL renderers rather than a stub.
This commit is contained in:
parent
a0910bcdef
commit
9e8d53bf3b
1 changed files with 57 additions and 2 deletions
|
|
@ -31,6 +31,15 @@ const REPO = process.env.DOXA_REPO
|
|||
|| 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')
|
||||
// The composable dynamically imports its renderers by RELATIVE path. Under a blob
|
||||
// URL there is no directory to resolve '../packet/...' against, so those imports
|
||||
// fail with "Failed to resolve module specifier" — a harness limitation, not a
|
||||
// product bug (Vite resolves them, and the built bundle inlines both renderers).
|
||||
// Read them here and substitute blob URLs so the gate exercises the REAL code.
|
||||
const packetRender = readFileSync(resolve(REPO, 'library/packet/packet-render.js'), 'utf8')
|
||||
const cardRender = readFileSync(resolve(REPO, 'library/packet/card-render.js'), 'utf8')
|
||||
const qr = readFileSync(resolve(REPO, 'library/packet/qr.js'), 'utf8')
|
||||
const strings = readFileSync(resolve(REPO, 'library/scripts/posters/packet-strings.json'), 'utf8')
|
||||
|
||||
const b = await chromium.launch()
|
||||
const pg = await b.newPage({ viewport: { width: 1000, height: 800 } })
|
||||
|
|
@ -38,16 +47,24 @@ 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 }) => {
|
||||
const r = await pg.evaluate(async ({ src, frame, packetRender, cardRender, qr, strings }) => {
|
||||
// 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 blob = (code) => URL.createObjectURL(new Blob([code], { type: 'text/javascript' }))
|
||||
const qrUrl = blob(qr)
|
||||
const packetUrl = blob(packetRender.replace("from './qr.js'", `from '${qrUrl}'`))
|
||||
const cardUrl = blob(cardRender.replace("from './qr.js'", `from '${qrUrl}'`))
|
||||
const stringsUrl = URL.createObjectURL(new Blob([`export default ${strings}`], { type: 'text/javascript' }))
|
||||
const patched = src
|
||||
.replace("from 'vue'", `from '${vueShim}'`)
|
||||
.replace("from './printInPlace.js'", `from '${frameUrl}'`)
|
||||
.replace("'../packet/packet-render.js'", `'${packetUrl}'`)
|
||||
.replace("'../packet/card-render.js'", `'${cardUrl}'`)
|
||||
.replace(/'\.\.\/scripts\/posters\/packet-strings\.json'/g, `'${stringsUrl}'`)
|
||||
const modUrl = URL.createObjectURL(new Blob([patched], { type: 'text/javascript' }))
|
||||
const { usePosterPrints } = await import(modUrl)
|
||||
|
||||
|
|
@ -84,13 +101,42 @@ const r = await pg.evaluate(async ({ src, frame }) => {
|
|||
out.listHasCheckbox = doc ? /□/.test(doc.body.innerText) : false
|
||||
out.listPhase = withProv.exportState.value?.phase ?? 'cleared'
|
||||
|
||||
// ── the PACKET action: same provider, the full booklet ───────────────────
|
||||
// Migrated from upg-100-list.vue, where it was a second hand-maintained copy of
|
||||
// the same renderer call. Asserted here so the shared version cannot regress
|
||||
// silently — the packet is the product's core artefact.
|
||||
const PKT = [
|
||||
{ slug: 'a', name: 'Aheu', country_code_label: 'Laos', population: 2700, image_url: 'x.jpg' },
|
||||
{ slug: 'b', name: 'Akha', country_code_label: 'Laos', population: 113000, image_url: 'y.jpg' },
|
||||
]
|
||||
printed = ''
|
||||
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 pk = usePosterPrints({ records: async () => PKT, label: (i) => i.label })
|
||||
await pk.printPacket({ id: 'laos', label: 'Laos' })
|
||||
await new Promise((r2) => setTimeout(r2, 600))
|
||||
document.createElement = realCreate
|
||||
const pdoc = new DOMParser().parseFromString(printed, 'text/html')
|
||||
out.packetSheets = pdoc.querySelectorAll('.sheet').length
|
||||
out.packetCards = pdoc.querySelectorAll('.card').length
|
||||
out.packetWheel = pdoc.querySelectorAll('svg path[fill^="hsl"]').length
|
||||
out.packetMark5 = /Gadarenes/.test(pdoc.body.innerText)
|
||||
|
||||
// ── 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 })
|
||||
}, { src, frame, packetRender, cardRender, qr, strings })
|
||||
await b.close()
|
||||
|
||||
const fails = []
|
||||
|
|
@ -103,10 +149,19 @@ if (r.noProvPhase !== 'error') {
|
|||
if (!/no records provider/i.test(r.noProvMsg)) {
|
||||
fails.push(`the no-provider message was "${r.noProvMsg}" — it should name the missing provider`)
|
||||
}
|
||||
// The packet is the product's core artefact: map page + people pages + points +
|
||||
// wheel. Assert its SHAPE, not merely that bytes were produced. With 2 groups
|
||||
// that is 1 map + 1 people page + points + wheel = 4 sheets.
|
||||
if (r.packetSheets !== 4) fails.push(`the packet printed ${r.packetSheets} sheets, expected 4`)
|
||||
if (r.packetCards !== 2) fails.push(`the packet printed ${r.packetCards} people cards, expected 2`)
|
||||
if (r.packetWheel !== 12) fails.push(`the prayer wheel has ${r.packetWheel} segments, expected 12`)
|
||||
if (!r.packetMark5) fails.push('the packet is missing Mark 5:1-20 in full')
|
||||
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(` packet : ${r.packetSheets} sheets, ${r.packetCards} cards, `
|
||||
+ `${r.packetWheel} wheel segments, Mark5=${r.packetMark5}`)
|
||||
console.log(` page errors : ${errs.length}`)
|
||||
|
||||
if (fails.length) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue