diff --git a/internal/check-upg-zone-joins.mjs b/internal/check-upg-zone-joins.mjs index 62329351f..e58828b31 100644 --- a/internal/check-upg-zone-joins.mjs +++ b/internal/check-upg-zone-joins.mjs @@ -18,6 +18,13 @@ * a plain string, while the source shape is `{ value, label }` before normalize.js * flattens it. Passing the object form matches nothing and returns an empty Set * for EVERY zone, which reads as "these zones have no groups". + * + 3. THE ZONE AND REGION ID SPACES OVERLAP. The legend emits ONE BARE STRING on one + * channel — a leaf emits its zone id, a region row emits the region's — and of 33 zone + * ids and 7 region ids exactly one name is shared, whose region is itself. Un-namespaced + * that string cannot say which row was clicked. It is harmless today ONLY because that + * region currently holds one zone, which is a fact about the data rather than a property + * of the design. * * EACH ARM IS CHECKED IN BOTH DIRECTIONS. The negative arms are the point: they record * what the fold and the flattening are BUYING, so a later reader asking "is this still @@ -28,6 +35,8 @@ import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { dirname, join } from 'node:path' import evaluate from '../plugin-designs/my-upg-100-list/data/selector-evaluator.js' +import { buildZoneTree, zonesForSelection, isRegionId, regionOf, humanize, REGION_PREFIX } + from '../plugin-designs/gospel-internet/src/zoneTree.js' import { zonesByGroup, zonesBySelector, toSelectorRecord } from '../plugin-designs/gospel-internet/src/upgZones.js' const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..') @@ -77,8 +86,45 @@ const obj = zonesBySelector(zoneLists, objShaped, evaluate) ok('selector join, OBJECT-shaped — zones with members (must be 0)', [...obj.members.values()].filter((s) => s.size).length, 0) +// ── 3. the zone/region id namespaces ──────────────────────────────────────── +// The fixture gives the colliding region TWO zones ON PURPOSE. Today's data gives it one, +// so both readings of the bare id return the same set and a check built on the real table +// would pass without ever exercising the difference — it would be measuring the data, not +// the code. regions' check watches the shipped tables and fails if the colliding set moves; +// this pins the behaviour that makes a collision harmless when it arrives. +const ZR = { + europe: 'europe', // the colliding name: a zone AND a region + 'europe-balkans': 'europe', // the second zone today's data does not yet have + 'asia-india-gujarat': 'asia', + brazil: 'latam', +} +// ?. and ?? -1 below are NOT defensive noise. Poisoning the namespace makes `region:asia` +// stop existing, and an unguarded `.count` THREW — the file died at the third assertion and +// the seven after it, including the one this section exists for, never ran. The crash still +// exited 1, so it read like the check catching the bug. A check that dies reports less than +// one that fails by name: guard the lookups so every arm runs and says what it saw. +const tree = buildZoneTree(ZR, { counts: { 'asia-india-gujarat': 7 } }) +ok('tree: every region row is namespaced', tree.every((n) => n.id.startsWith(REGION_PREFIX)) ? 1 : 0, 1) +ok('tree: leaves stay bare', tree.flatMap((n) => n.children).every((c) => !isRegionId(c.id)) ? 1 : 0, 1) +ok('tree: a region rolls up its children counts', tree.find((n) => n.id === 'region:asia')?.count ?? -1, 7) +const asLeaf = zonesForSelection('europe', ZR) +const asRegion = zonesForSelection('region:europe', ZR) +ok('tree: the bare id means that ZONE only', asLeaf?.length ?? -1, 1) +ok('tree: the namespaced id expands to the REGION\'s zones', asRegion?.length ?? -1, 2) +// A SUMMARY ARM, AND THE WEAKEST ONE — kept for what it says, not for what it catches. +// Under a poisoned namespace the two readings still DIFFER (2 vs 0), both wrong, so this +// arm stays green while the two above it go red. The specific lengths are the guards; this +// line only states the property they add up to. Labelled honestly so nobody trusts it alone. +ok('tree: the two readings of the colliding name are not the same set (summary)', + JSON.stringify(asLeaf) !== JSON.stringify(asRegion) ? 1 : 0, 1) +ok('tree: a bare id never reads as a region', (!isRegionId('europe') && regionOf('europe') === null) ? 1 : 0, 1) +const sweep = new Set(tree.flatMap((n) => zonesForSelection(n.id, ZR))) +ok('tree: sweeping every region reaches every FILED zone and no more', sweep.size, Object.keys(ZR).length) +ok('tree: there is no region-based "all" to misuse', zonesForSelection(null, ZR) === null ? 1 : 0, 1) +ok('tree: humanize is only a fallback label', humanize('asia-india-gujarat') === 'Asia India Gujarat' ? 1 : 0, 1) + if (fail.length) { console.log(`\nUPG-ZONE-JOINS: FAIL — ${fail.length} assertion(s): ${fail.join(', ')}`) process.exit(1) } -console.log('\nUPG-ZONE-JOINS: PASS — both joins pinned in both directions') +console.log('\nUPG-ZONE-JOINS: PASS — both joins pinned in both directions, and the id namespaces cannot collapse')