check:poster-sections: the presets must equal each poster type's own regions
The presets file now has to hold, per poster type, exactly an all-regions preset plus one per region the type's adapter returns from scopes(). A region the data drops (a stale "other") or gains then fails the check, instead of lingering as a preset. 'country' is measured always; 'upg100-list' once its type file is on the tree. Self-test: a preset for a region the type does not have must fail.
This commit is contained in:
parent
c6425ed1cf
commit
6060694545
2 changed files with 55 additions and 6 deletions
|
|
@ -693,8 +693,8 @@
|
|||
"check:side-lanes:self-test": "a poster-render.js copy WITHOUT the lane clamp must make check:side-lanes' crossing arm FAIL on the same seeded data (it does: 36 crossings), so the arm is shown able to fail.",
|
||||
"check:poster-section": "the reusable poster section (<dxm-poster-section>): the type registry has 'country'; DX Life's /regions slugs (and every other spelling of a region) each show their countries, every country once; a withheld whole-country poster stays withheld through the type; the element's attributes map to its config; PosterSection.vue injects every name its template uses.",
|
||||
"check:poster-section:self-test": "a registry without 'country', a region key without \"&\" → \"and\", and a section that uses a name it does not inject must each make check:poster-section fail.",
|
||||
"check:poster-sections": "the Poster Sections page (plugin-designs/poster-sections): every panel choice (each region or All regions, each language, open or collapsed, 1-5 per row) builds <dxm-poster-section> tags that the element's own attribute reader turns back into exactly those choices; All regions is one section per region; Copy embed is the bundle script plus those same tags.",
|
||||
"check:poster-sections:self-test": "a tag builder that drops columns, one that drops collapsed, and one that repeats a region under All regions must each make check:poster-sections fail.",
|
||||
"check:poster-sections": "the Poster Sections page (plugin-designs/poster-sections): every panel choice (each region or All regions, each language, open or collapsed, 1-5 per row) builds <dxm-poster-section> tags that the element's own attribute reader turns back into exactly those choices; All regions is one section per region; Copy embed is the bundle script plus those same tags. The presets file holds, per poster type, exactly an all-regions preset plus one per region its adapter's scopes() returns, so a region the data drops (or gains) cannot drift from the presets.",
|
||||
"check:poster-sections:self-test": "a tag builder that drops columns, one that drops collapsed, one that repeats a region under All regions, and a preset for a region the type does not have must each make check:poster-sections fail.",
|
||||
"check:side-lane-photo-pixels": "BROWSER gate (browser seat; elsewhere 75 NOT MEASURED): the 60x96 side-lane sheet rasterised by poster-raster.js, measured in pixels at every photo slot. A data: photo draws; every photo on its own host stays flat, never the broken-image glyph an external <image> rasterises as.",
|
||||
"check:side-lane-photo-pixels:self-test": "the same raster WITHOUT stripExternalImages must leave the external photo slots NOT flat (the glyph), else the gate proves nothing."
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,8 +3,12 @@
|
|||
// For every choice the panel offers (type, one region or All regions, each language, collapsed or open, 1-5 per
|
||||
// row), the tags src/embed.js builds, read back through the ELEMENT's own attribute reader (posterSectionConfig),
|
||||
// give exactly those choices; "All regions" is one section per region, each once; the snippet's script and tags
|
||||
// are the same tags the page renders. --self-test: a builder that drops `columns`, one that drops `collapsed`, and
|
||||
// one that repeats a region under All must each FAIL.
|
||||
// are the same tags the page renders.
|
||||
// PRESETS: poster-sections.instances.json has, per poster type, exactly "<prefix>-all" + one preset per region its
|
||||
// adapter's scopes() returns, no more, no fewer. So a region the data no longer has (a stale "other") or a new one
|
||||
// cannot drift from the presets. 'country' is always measured; 'upg100-list' once its type file is on the tree.
|
||||
// --self-test: a builder that drops `columns`, one that drops `collapsed`, one that repeats a region under All, and a
|
||||
// presets file with a region the type does not have must each FAIL.
|
||||
import { readdirSync, statSync } from 'node:fs'
|
||||
import { join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
|
@ -12,7 +16,45 @@ import { fileURLToPath } from 'node:url'
|
|||
const LOC = join(dirname(fileURLToPath(import.meta.url)), '../../../library/i18n/locales')
|
||||
const SUPPORTED_LOCALES = readdirSync(LOC).filter((d) => statSync(join(LOC, d)).isDirectory()).sort()
|
||||
import { posterSectionConfig } from '../../../library/elements/posterSectionConfig.js'
|
||||
import { readFileSync, existsSync } from 'node:fs'
|
||||
import * as real from '../src/embed.js'
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '../../..')
|
||||
const PRESETS = JSON.parse(readFileSync(join(ROOT, 'plugin-designs/poster-sections/poster-sections.instances.json'), 'utf8'))
|
||||
// poster type → [preset id prefix, the file that defines the type's adapter (its default export has scopes())]
|
||||
const TYPES = { country: ['countries', 'library/components/poster/types/countryPosterType.js'], 'upg100-list': ['upg100', 'plugin-designs/my-upg-100-list/data/upg100-poster-type.js'] }
|
||||
|
||||
// Each type's scopes(), loaded through Vite (the adapters import through the @map alias).
|
||||
async function loadScopes() {
|
||||
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') } } })
|
||||
const out = {}
|
||||
try {
|
||||
for (const [type, [, file]] of Object.entries(TYPES)) {
|
||||
if (!existsSync(join(ROOT, file))) { out[type] = null; continue }
|
||||
const a = (await server.ssrLoadModule(join(ROOT, file))).default
|
||||
out[type] = a.scopes()
|
||||
}
|
||||
} finally { await server.close() }
|
||||
return out
|
||||
}
|
||||
|
||||
function presetArm(presets, scopesByType) {
|
||||
const fails = []
|
||||
for (const [type, [prefix]] of Object.entries(TYPES)) {
|
||||
const scopes = scopesByType[type]
|
||||
const ids = presets.instances.filter((i) => i.props && i.props.type === type).map((i) => i.id)
|
||||
if (scopes == null) continue // the type's file is not on this tree yet
|
||||
const want = [`${prefix}-all`, ...scopes.map((s) => `${prefix}-${s}`)].sort()
|
||||
const have = [...ids].sort()
|
||||
const extra = have.filter((x) => !want.includes(x)), missing = want.filter((x) => !have.includes(x))
|
||||
if (extra.length || missing.length) fails.push(`presets for "${type}" differ from its scopes(): extra ${extra.join(', ') || '-'}; missing ${missing.join(', ') || '-'}`)
|
||||
for (const i of presets.instances.filter((x) => x.props && x.props.type === type && x.id !== `${prefix}-all`)) {
|
||||
if (i.id !== `${prefix}-${i.props.scope}`) fails.push(`preset "${i.id}" shows scope "${i.props.scope}"`)
|
||||
}
|
||||
}
|
||||
if (!presets.instances.some((i) => i.id === 'configure' && i.props && i.props.configure === true)) fails.push('no "configure" preset')
|
||||
return fails
|
||||
}
|
||||
|
||||
const SCOPES = ['africa', 'asia', 'europe', 'latin-america-and-caribbean', 'middle-east', 'north-america-and-non-spanish-caribbean', 'oceania', 'other']
|
||||
const fake = (attrs) => ({ getAttribute: (n) => (n in attrs ? attrs[n] : null), hasAttribute: (n) => n in attrs })
|
||||
|
|
@ -39,6 +81,8 @@ function arms(embed) {
|
|||
return fails
|
||||
}
|
||||
|
||||
const SCOPES_BY_TYPE = await loadScopes()
|
||||
|
||||
if (process.argv.includes('--self-test')) {
|
||||
const mutants = {
|
||||
'drops columns': { ...real, sectionAttrs: (c) => real.sectionAttrs(c).map(({ columns, ...a }) => a) },
|
||||
|
|
@ -46,6 +90,10 @@ if (process.argv.includes('--self-test')) {
|
|||
'repeats a region under All': { ...real, sectionAttrs: (c) => { const r = real.sectionAttrs(c); return c.scope ? r : [...r, r[0]] } },
|
||||
}
|
||||
let ok = true
|
||||
const stale = { ...PRESETS, instances: [...PRESETS.instances, { id: 'countries-atlantis', props: { type: 'country', scope: 'atlantis' } }] }
|
||||
const pf = presetArm(stale, SCOPES_BY_TYPE)
|
||||
console.log(` mutant "a preset for a region the type does not have": ${pf.length ? 'FAILS (good)' : 'PASSES (bad)'}`)
|
||||
if (!pf.length) ok = false
|
||||
for (const [name, m] of Object.entries(mutants)) {
|
||||
const f = arms(m)
|
||||
console.log(` mutant "${name}": ${f.length ? 'FAILS (good)' : 'PASSES (bad)'}`)
|
||||
|
|
@ -54,6 +102,7 @@ if (process.argv.includes('--self-test')) {
|
|||
console.log(ok ? 'POSTER-SECTIONS SELF-TEST: PASS — every mutant fails the check.' : 'POSTER-SECTIONS SELF-TEST: FAIL')
|
||||
process.exit(ok ? 0 : 1)
|
||||
}
|
||||
const fails = arms(real)
|
||||
const fails = [...arms(real), ...presetArm(PRESETS, SCOPES_BY_TYPE)]
|
||||
const measured = Object.entries(SCOPES_BY_TYPE).filter(([, v]) => v).map(([k, v]) => `${k} ${v.length}`).join(', ')
|
||||
if (fails.length) { console.log(`POSTER-SECTIONS: FAIL (${fails.length})`); fails.forEach((f) => console.log(` - ${f}`)); process.exit(1) }
|
||||
console.log(`POSTER-SECTIONS: PASS — every panel choice (${SCOPES.length + 1} region choices × ${SUPPORTED_LOCALES.length} languages × open/collapsed × 5 per row) reads back through the element unchanged; All regions = one section per region; the snippet is the script + those tags.`)
|
||||
console.log(`POSTER-SECTIONS: PASS — every panel choice (${SCOPES.length + 1} region choices × ${SUPPORTED_LOCALES.length} languages × open/collapsed × 5 per row) reads back through the element unchanged; All regions = one section per region; the snippet is the script + those tags; presets = each type's scopes() + all (${measured}).`)
|
||||
|
|
|
|||
Loading…
Reference in a new issue