UPG-100's poster wall groups its zones into regions (India, China + Mongolia,
Africa, Middle East, Central Asia, Southeast Asia + Oceania, Americas, Europe).
Those sections are now collapsible like Country Posters' and start open. They
are also available on their own as <dxm-poster-section type="upg100-list"
scope="africa">.
- data/upg100-regions.js (pure): the region table (REGION_OF, moved verbatim
from poster-wall.vue), regionOf(id) and each region's scope slug.
- data/upg100-poster-type.js: the poster-type adapter (scopes, items, title,
actions), built on the wall's own zones (upg100Zones), headings (regionTitle)
and print path (the records provider with this plugin's evaluator, and
zone-sheet-print.js).
- index.js registers the type ('upg100-list') and the element. The element
registration is a no-op if another bundle on the page registered it first.
- poster-wall.vue groups through the same regionOf, draws the same zones, and
passes collapsible + collapsedBy (all open). Its other props are unchanged.
check:upg100-poster-type (gates:pre) checks:
- every zone sits in a named region, with a negative control;
- the 8 sections exist, and the scopes are unique and round-trip;
- the headings exist ×11;
- the wiring holds.
Three mutants each fail it.
No library code changed.
84 lines
5.7 KiB
JavaScript
84 lines
5.7 KiB
JavaScript
#!/usr/bin/env node
|
|
// check-upg100-poster-type — UPG-100's region sections are ONE section, whether the poster wall draws them or
|
|
// <dxm-poster-section type="upg100-list" scope="…"> does. Headless. Asserts:
|
|
// A. every zone the wall shows (clusters.json: lists + asia_lists + uncovered_lists) belongs to a NAMED region,
|
|
// never 'Other', and the sections the wall promises exist (India, China + Mongolia, Africa, Middle East,
|
|
// Central Asia, Southeast Asia + Oceania, Americas, Europe). NEGATIVE CONTROL: a planted unknown zone falls to
|
|
// 'Other', so this check can fail;
|
|
// B. each region's scope (its slug) is unique and round-trips from the English name, so a scope can't name two
|
|
// sections;
|
|
// C. each region's heading has its poster.region_* key in all 11 locales;
|
|
// D. the wiring: the bundle registers the 'upg100-list' type and the element; the type's adapter has the four
|
|
// parts a section reads; the wall groups through the SAME regionOf and draws the SAME zones (upg100Zones), and
|
|
// its sections are collapsible.
|
|
import { readFileSync } from 'node:fs'
|
|
import { resolve, dirname, join } from 'node:path'
|
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
|
|
const REPO = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
const P = (...a) => join(REPO, ...a)
|
|
const PLUGIN = 'plugin-designs/my-upg-100-list'
|
|
const fails = [], notes = []
|
|
const ok = (cond, msg, note) => { if (!cond) fails.push(msg); else if (note) notes.push(` ok ${note}`) }
|
|
const LANGS = ['en', 'es', 'fr', 'pt', 'de', 'it', 'ro', 'ru', 'ar', 'hi', 'zh']
|
|
|
|
let R, clusters, REGION_KEY
|
|
try {
|
|
R = await import(pathToFileURL(P(PLUGIN, 'data/upg100-regions.js')).href)
|
|
clusters = JSON.parse(readFileSync(P(PLUGIN, 'data/clusters.json'), 'utf8'))
|
|
// REGION_KEY is a plain table in legend-tree.js; read it from the source rather than importing the module (it
|
|
// pulls JSON the node loader cannot take without import attributes).
|
|
const src = readFileSync(P(PLUGIN, 'data/legend-tree.js'), 'utf8')
|
|
REGION_KEY = Object.fromEntries([...src.slice(src.indexOf('export const REGION_KEY'), src.indexOf('};', src.indexOf('export const REGION_KEY'))).matchAll(/'([^']+)': '([^']+)'/g)].map((m) => [m[1], m[2]]))
|
|
} catch (e) {
|
|
console.error(`UPG100-POSTER-TYPE: NOT MEASURED (exit 75) — an input did not load: ${e.message}`)
|
|
process.exit(75)
|
|
}
|
|
|
|
// ── A ──
|
|
const ids = [...(clusters.lists || []), ...(clusters.asia_lists || []), ...(clusters.uncovered_lists || [])].map((l) => l.id).filter(Boolean)
|
|
const others = ids.filter((id) => R.regionOf(id) === 'Other')
|
|
const used = new Set(ids.map((id) => R.regionOf(id)))
|
|
const PROMISED = ['India', 'China + Mongolia', 'Africa', 'Middle East', 'Central Asia', 'Southeast Asia + Oceania', 'Americas', 'Europe']
|
|
ok(R.regionOf('a-zone-nobody-placed') === 'Other', 'NEGATIVE CONTROL: an unknown zone did not fall to Other, so this check cannot see a missing region',
|
|
'negative control: an unplaced zone falls to Other')
|
|
ok(ids.length > 20 && !others.length && PROMISED.every((r) => used.has(r)),
|
|
`zones without a region: ${others.join(', ') || '(none)'}; sections missing: ${PROMISED.filter((r) => !used.has(r)).join(', ') || '(none)'}`,
|
|
`${ids.length} zones, every one in a named region; all ${PROMISED.length} sections present`)
|
|
|
|
// ── B ──
|
|
const scopes = R.REGIONS.map(R.regionScope)
|
|
ok(new Set(scopes).size === scopes.length && R.REGIONS.every((r) => R.regionScope(R.regionScope(r)) === R.regionScope(r)) && R.regionScope('Southeast Asia + Oceania') === 'southeast-asia-oceania',
|
|
`scopes are not unique or do not round-trip: ${scopes.join(', ')}`, `scopes unique and stable: ${scopes.join(', ')}`)
|
|
|
|
// ── C ──
|
|
const missingKeys = []
|
|
for (const r of R.REGIONS) {
|
|
const key = REGION_KEY[r]
|
|
if (!key) { missingKeys.push(`${r}: no REGION_KEY`); continue }
|
|
for (const l of LANGS) { const cat = JSON.parse(readFileSync(P('library/i18n/locales', l, 'poster.json'), 'utf8')); if (typeof cat[key] !== 'string' || !cat[key].trim()) missingKeys.push(`${l} poster.${key}`) }
|
|
}
|
|
ok(!missingKeys.length, `region headings missing: ${missingKeys.slice(0, 6).join(', ')}`, `every region heading exists in all ${LANGS.length} locales`)
|
|
|
|
// ── D ──
|
|
const index = readFileSync(P(PLUGIN, 'index.js'), 'utf8')
|
|
const type = readFileSync(P(PLUGIN, 'data/upg100-poster-type.js'), 'utf8')
|
|
const wall = readFileSync(P(PLUGIN, 'poster-wall.vue'), 'utf8')
|
|
const wired = {
|
|
'the bundle registers the upg100-list type': /registerPosterType\('upg100-list', \(\) => import\('\.\/data\/upg100-poster-type\.js'\)\)/.test(index),
|
|
'the bundle registers the element': /\nregisterPosterSectionElement\(\)/.test(index),
|
|
'the adapter has scopes, items, title and actions': ['scopes:', 'items(scope, locale)', 'title(scope, t)', 'actions({'].every((s) => type.includes(s)),
|
|
'the wall groups through the same regionOf': /const regionFor = \(id\) => regionTitle\(regionOf\(id\), t\)/.test(wall) && !/const REGION_OF = /.test(wall),
|
|
'the wall draws the same zones': /return upg100Zones\(profileConfig, locale\)/.test(wall),
|
|
'the wall sections are collapsible': /:collapsible="true"/.test(wall) && /:collapsed-by="allOpen"/.test(wall),
|
|
}
|
|
const missingWires = Object.entries(wired).filter(([, v]) => !v).map(([k]) => k)
|
|
ok(!missingWires.length, `missing: ${missingWires.join('; ')}`, 'the wall and <dxm-poster-section type="upg100-list"> share one region table, one zone list and the collapsible section')
|
|
|
|
for (const n of notes) console.log(n)
|
|
if (fails.length) {
|
|
console.error('UPG100-POSTER-TYPE: FAIL')
|
|
for (const f of fails) console.error(` ✗ ${f}`)
|
|
process.exit(1)
|
|
}
|
|
console.log('UPG100-POSTER-TYPE: PASS — UPG-100\'s region sections are one section, on the wall and as <dxm-poster-section type="upg100-list">.')
|