It is not released yet, and the pilot syncs to DX Life (and the partner handoff) within minutes. So WITHHELD.IND is
{ poster: true, rows: true, preview: true }: posterWithheld() is false ONLY when the host page sets
globalThis.DXM_PREVIEW_UNRELEASED === true (the Plugin Studio on the pilot, where it is reviewed); never by a URL
parameter. Every other host keeps the withheld sentence on India's card, and Country Posters' India tab exists only
under that flag. The rows (list, packet, cards) stay withheld everywhere.
check:country-sheet-one-path arm G proves (a) withheld by default and (b) printing at 66x66 with the flag (only
=== true opens it), rows withheld in both, the India tab gated, with a poison for each; check:poster-section arm C
expects the sentence by default.
99 lines
7 KiB
JavaScript
99 lines
7 KiB
JavaScript
#!/usr/bin/env node
|
|
// check:poster-section — the reusable poster section (<dxm-poster-section>) and the lift behind it.
|
|
// A. the TYPE REGISTRY: 'country' is registered, an unknown type is null, a plugin can register its own.
|
|
// B. SCOPES: DX Life's /regions slugs (africa … oceania, other) each resolve; every spelling of a region (its
|
|
// slug, its API value, its English name) resolves to the same countries; the scopes cover all countries once.
|
|
// C. India through the type, by default: its whole-country POSTER card shows the withheld sentence; its ROWS are
|
|
// rejected with it, no records fetched.
|
|
// D. the ELEMENT's attributes map to its config (collapsible on by default, collapsible="false", collapsed).
|
|
// E. THE LIFT: every name PosterSection.vue's template uses is injected from the wall (a section never reads
|
|
// something its wall does not provide).
|
|
// --self-test: a registry without 'country', a region key without "&" → "and", and a section template that uses a
|
|
// name it does not inject must each FAIL. Loaded through Vite (the @map alias), like check:poster-wall-host-seams.
|
|
import { readFileSync } from 'node:fs'
|
|
import { join, dirname } from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..')
|
|
const DXL = ['africa', 'asia', 'europe', 'latin-america-and-caribbean', 'middle-east', 'north-america-and-non-spanish-caribbean', 'oceania', 'other']
|
|
|
|
async function arms({ types, country, element, sectionSrc }) {
|
|
const fails = []
|
|
// A
|
|
if (!types.posterTypeNames().includes('country')) fails.push("A: the registry has no 'country' type")
|
|
if (await types.posterType('no-such-type') !== null) fails.push('A: an unknown type is not null')
|
|
types.registerPosterType('gate-fixture', { items: () => [{ id: 'x' }], title: () => 'X', actions: () => ({}) })
|
|
if (!(await types.posterType('gate-fixture'))) fails.push('A: a registered type cannot be found')
|
|
// B
|
|
const data = JSON.parse(readFileSync(join(ROOT, 'library/data/country-posters.json'), 'utf8')).countries
|
|
const seen = new Map()
|
|
for (const k of DXL) {
|
|
const ids = country.items(k, 'en').map((c) => c.id)
|
|
if (!ids.length) fails.push(`B: scope "${k}" (a DX Life /regions slug) shows no countries`)
|
|
for (const id of ids) seen.set(id, (seen.get(id) || 0) + 1)
|
|
}
|
|
const once = data.filter((c) => seen.get(c.id) === 1).length
|
|
if (once !== data.length) fails.push(`B: the scopes cover ${once} of ${data.length} countries exactly once`)
|
|
for (const [a, b, c] of [['latin-america-and-caribbean', 'latin_america_&_caribbean', 'Latin America & Caribbean'], ['middle-east', 'middle_east', 'Middle East']]) {
|
|
const s = (x) => country.items(x, 'en').map((r) => r.id).join()
|
|
if (!(s(a) && s(a) === s(b) && s(b) === s(c))) fails.push(`B: "${a}" / "${b}" / "${c}" do not resolve to the same countries`)
|
|
}
|
|
// C
|
|
const ind = country.items('asia', 'en').find((c) => c.iso3 === 'IND')
|
|
if (!ind) fails.push('C: India is not in the asia scope')
|
|
else {
|
|
let fetched = 0
|
|
const act = country.actions({ assetsBase: () => '', apiBase: () => { fetched++; return '' }, lang: () => 'en', profileConfig: null, t: (k) => k })
|
|
if (act.unsupported(ind) !== 'messages.countryPosterTooMany') fails.push("C: India's whole-country poster card does not show the withheld sentence by default (it is not released)")
|
|
let why = ''
|
|
try { await act.records(ind); why = 'resolved' } catch (e) { why = e && (e.i18nKey || e.message) }
|
|
if (!/countryPosterTooMany/.test(String(why))) fails.push(`C: withheld India's rows are not withheld (${why})`)
|
|
if (fetched) fails.push('C: withheld India fetched records')
|
|
}
|
|
// D
|
|
const fake = (attrs) => ({ getAttribute: (n) => (n in attrs ? attrs[n] : null), hasAttribute: (n) => n in attrs })
|
|
const d0 = element.posterSectionConfig(fake({ scope: 'africa' }))
|
|
if (!(d0.type === 'country' && d0.scope === 'africa' && d0.collapsible === true && d0.collapsed === false)) fails.push(`D: defaults wrong ${JSON.stringify(d0)}`)
|
|
if (element.posterSectionConfig(fake({ collapsible: 'false' })).collapsible !== false) fails.push('D: collapsible="false" does not turn collapsing off')
|
|
if (element.posterSectionConfig(fake({ collapsed: '' })).collapsed !== true) fails.push('D: collapsed does not start the section shut')
|
|
// E
|
|
const tpl = (sectionSrc.match(/<template>([\s\S]*)<\/template>\s*$/) || ['', ''])[1].replace(/<!--[\s\S]*?-->/g, '')
|
|
const injected = new Set(((sectionSrc.match(/const \{([\s\S]*?)\} = inject\(POSTER_WALL_CTX\)/) || ['', ''])[1]).split(/[\s,]+/).filter(Boolean))
|
|
const used = new Set()
|
|
for (const m of tpl.matchAll(/(?:[:@]|v-[a-z.-]+)[\w.-]*="([^"]*)"|\{\{([^}]*)\}\}/g)) {
|
|
for (const e of [m[1], m[2]]) { if (!e) continue
|
|
for (const id of e.replace(/'[^']*'|`[^`]*`/g, '').matchAll(/(?<![\w.$])([A-Za-z_$][\w$]*)/g)) used.add(id[1]) }
|
|
}
|
|
const local = new Set(['sec', 'z', 'item', 'n', 's', 'v', 'el', 'true', 'false', 'null', 'String', 'Number', '$event', 'in', 'label', 'zIndex'])
|
|
const missing = [...used].filter((x) => !local.has(x) && !injected.has(x))
|
|
if (missing.length) fails.push(`E: PosterSection.vue uses names it does not inject: ${missing.join(', ')}`)
|
|
return fails
|
|
}
|
|
|
|
const { createServer } = await import('vite')
|
|
const server = await createServer({ root: ROOT, configFile: false, logLevel: 'error', appType: 'custom', server: { middlewareMode: true, hmr: false, ws: false }, resolve: { alias: { '@map': join(ROOT, 'library') } } })
|
|
let code = 0
|
|
try {
|
|
const load = (p) => server.ssrLoadModule(join(ROOT, p))
|
|
const types = await load('library/components/poster/posterTypes.js')
|
|
const country = (await load('library/components/poster/types/countryPosterType.js')).default
|
|
const element = await load('library/elements/posterSectionConfig.js')
|
|
const sectionSrc = readFileSync(join(ROOT, 'library/components/poster/PosterSection.vue'), 'utf8')
|
|
const real = { types, country, element, sectionSrc }
|
|
if (process.argv.includes('--self-test')) {
|
|
const poisons = [
|
|
["a registry without 'country'", { ...real, types: { ...types, posterTypeNames: () => [] } }, /^A:/],
|
|
['a region key without "&" → "and"', { ...real, country: { ...country, items: (s, l) => (/-and-/.test(String(s)) ? [] : country.items(s, l)) } }, /^B:/],
|
|
['a section that uses a name it does not inject', { ...real, sectionSrc: sectionSrc.replace('</template>', '{{ notProvided }}</template>') }, /^E:/],
|
|
]
|
|
for (const [name, inp, rx] of poisons) {
|
|
const f = await arms(inp); const ok = f.some((x) => rx.test(x))
|
|
console.log(ok ? `✓ caught: ${name} -> ${f.find((x) => rx.test(x))}` : `✗ MISSED: ${name}`)
|
|
if (!ok) code = 1
|
|
}
|
|
} else {
|
|
const f = await arms(real)
|
|
if (f.length) { f.forEach((x) => console.log('FAIL ' + x)); code = 1 }
|
|
else console.log(`PASS check:poster-section — the 'country' type; ${DXL.length} DX Life scopes cover every country once (and their other spellings); India withheld by default; the element's attributes; PosterSection injects every name it uses`)
|
|
}
|
|
} finally { await server.close() }
|
|
process.exit(code)
|