PosterWall: sectionItemCountKey, so one section can be counted in its own words
The Country Posters India section headed "855 people groups · 37
countries", which is untrue: its cards are India's own and one per state
and union territory. sectionItemCountKey (default null: every section
counted with item-count-key, as before) lets a wall answer, per section,
an i18n plural key, or { key, n } to count n rather than the section's
card count, or null to keep the default.
Walls that do not pass it render byte-identical trees before and after
(a differential render of an upg100-style and a country-posters-style
wall). check:poster-wall-host-seams arm K: a named section counts in its
own key and number, an unnamed one keeps the default; it fails with the
prop ignored.
This commit is contained in:
parent
d3ceb95711
commit
f3851da57c
3 changed files with 32 additions and 4 deletions
|
|
@ -40,6 +40,8 @@
|
|||
* I (previewSrc, the generated image fails) one error shows the placeholder: there is no second base.
|
||||
* J (section heads) a section with people (total > 0) reads "<n people groups> · <items>", as it always has;
|
||||
* a section with none (a template gallery) reads "<items>" alone: no 0 count, no " · ".
|
||||
* K (sectionItemCountKey) a section the callback names is counted in its own words and number ({ key, n });
|
||||
* a section it answers null for keeps the default count.
|
||||
* embedBase.js is served to the gate as if from a BUILT tree (<tree>/<bundle>/<bundle>.js), because
|
||||
* PosterWall's second base is derived from that URL; from source it is empty and H could not fail.
|
||||
* A vacuous run (no .tape-h found, or no items rendered) FAILS.
|
||||
|
|
@ -356,6 +358,22 @@ try {
|
|||
if (!without || /·/.test(without) || /\b0\b/.test(without) || /people/i.test(without)) fail(`J: a section with no people should read its items alone, got ${JSON.stringify(without)}`)
|
||||
app.unmount()
|
||||
}
|
||||
|
||||
// K: one section counted in its own words (India: its states, not countries); the others unchanged
|
||||
{
|
||||
const groups = { k1: 'India', k2: 'India', k3: 'India', k4: 'Asia', k5: 'Asia' }
|
||||
const root = node('root')
|
||||
const app = createApp(PosterWall, { instanceId: 'gate-k', groupBy: (x) => groups[x.id],
|
||||
sectionItemCountKey: (sec) => (sec.name === 'India' ? { key: 'regions.countryCount', n: 5 } : null),
|
||||
items: Object.keys(groups).map((id, i) => ({ id, label: id, count: i + 1 })) })
|
||||
app.mount(root); await rc.nextTick()
|
||||
const heads = {}
|
||||
walk(root, (n) => { if (hasClass(n, 'sec-head')) heads[textOf(n).includes('India') ? 'India' : 'Asia'] = textOf(n) })
|
||||
if (!/\b5 countries\b/.test(heads.India || '')) fail(`K: the named section should count 5 in its own key, got ${JSON.stringify(heads.India)}`)
|
||||
if (/\b3 list/.test(heads.India || '')) fail('K: the named section still shows the default count')
|
||||
if (!/\b2 lists?\b/.test(heads.Asia || '')) fail(`K: an unnamed section should keep the default count, got ${JSON.stringify(heads.Asia)}`)
|
||||
app.unmount()
|
||||
}
|
||||
} catch (e) {
|
||||
fail(`threw: ${e?.stack || e}`)
|
||||
} finally {
|
||||
|
|
@ -367,5 +385,5 @@ if (failures.length) {
|
|||
console.error(`POSTER-WALL-HOST-SEAMS: FAIL (${failures.length})\n - ${failures.join('\n - ')}`)
|
||||
process.exitCode = 1
|
||||
} else {
|
||||
console.log(`POSTER-WALL-HOST-SEAMS: PASS (10 arms, ${buttonsSeen} stickers rendered; defaults unchanged: the Show-on-map toggle and the stored webp; headerAction emits, previewSrc is lazy, one at a time, cached and bounded)`)
|
||||
console.log(`POSTER-WALL-HOST-SEAMS: PASS (11 arms, ${buttonsSeen} stickers rendered; defaults unchanged: the Show-on-map toggle and the stored webp; headerAction emits, previewSrc is lazy, one at a time, cached and bounded)`)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,6 +204,11 @@ const props = defineProps({
|
|||
// null, a throw or no answer within PREVIEW_TIMEOUT_MS shows the "preview unavailable" placeholder.
|
||||
// The page keeps each returned URL and never revokes it (see GENERATED PREVIEWS below).
|
||||
previewSrc: { type: Function, default: null },
|
||||
// ★ ONE SECTION COUNTED IN ITS OWN WORDS. null (the default) counts every section's cards with item-count-key, as
|
||||
// always. A wall whose section holds a different kind of card passes (sec) => an i18n plural key, or { key, n }
|
||||
// to count n rather than the section's card count, or null for that section to keep the default. The Country
|
||||
// Posters India section holds India's own card and one per state and union territory, which are not countries.
|
||||
sectionItemCountKey: { type: Function, default: null },
|
||||
})
|
||||
const emit = defineEmits(['header-action'])
|
||||
|
||||
|
|
@ -317,9 +322,14 @@ const itemCount = (n) => (props.itemCountKey ? countOf(props.itemCountKey, n) :
|
|||
// A section head's count line. The people-group total and its " · " leave together when the total is 0 (a gallery
|
||||
// of poster templates counts no people), the rule the cards already follow with v-if="item.count". One string in
|
||||
// one interpolation, so a head with a total renders exactly the text it always did, whitespace included.
|
||||
const sectionItems = (sec) => {
|
||||
const own = props.sectionItemCountKey ? props.sectionItemCountKey(sec) : null
|
||||
if (!own) return itemCount(sec.items.length)
|
||||
return typeof own === 'string' ? countOf(own, sec.items.length) : countOf(own.key, own.n ?? sec.items.length)
|
||||
}
|
||||
const sectionCount = (sec) => (sec.total > 0
|
||||
? `${countOf('wall.peopleGroupCount', sec.total)} · ${itemCount(sec.items.length)}`
|
||||
: itemCount(sec.items.length))
|
||||
? `${countOf('wall.peopleGroupCount', sec.total)} · ${sectionItems(sec)}`
|
||||
: sectionItems(sec))
|
||||
const base = computed(() => String(props.assetsBase || './assets').replace(/\/+$/, ''))
|
||||
|
||||
// ★ A SECOND PLACE TO LOOK, because "poster not generated yet" was being shown for posters that
|
||||
|
|
|
|||
|
|
@ -670,7 +670,7 @@
|
|||
"check:popup-pan-controls": "the popup auto-pan reserves the map's bottom control band, so a popup that clears the CLIP is also clear of the things that take the click. Measured at 390 on a 366x732 map: the pan fitted the popup inside the container, cut 0, and a real click on Full Profile still did nothing because the top element at the button's centre was div.maplibregl-ctrl-attrib-inner (and span.pin-hint-pill on the sibling pin) - a cut-height check cannot see this, because the cut really was zero. Attribution is a licence term of the basemap, so the popup may not be raised over it; the usable frame is shorter than the container and the fit test says so. Runs the SHIPPED panPlanFor, read out of the SFC with new Function (it is not exported), never a re-typed copy, and asserts where the popup ENDS UP rather than re-deriving the expected number from the same formula. Sweeps band heights 0-120px and every width the component renders at. Source-only, headless.",
|
||||
"check:control-inset": "bottomControlInset MEASURES the map's real bottom control band, so the pan reserves the band the controls actually occupy. check:popup-pan-controls proves the ARITHMETIC; this proves the band is real. If the inset silently returned 0 -- a selector typo, an engine renaming its control classes, a control moving outside the container -- panPlanFor would get bottomInset 0, behave exactly as it did before the fix, and check:popup-pan-controls would still pass every case, because its arithmetic is still correct: the bug back with both gates green. 8 cases against a real DOM, running the SHIPPED bottomControlInset read out of the SFC (it is not exported), never a copy: the band equals what the DOM independently says (not merely non-zero); no controls reserves 0; a zero-sized control reserves nothing; the taller of two corners wins; a wrapped attribution reserves more than one line; a control outside the container is ignored; a null container returns 0; and the MAPBOX corners count too, since the component takes a provider prop and every other case renders MapLibre names. Needs a browser: every assertion rests on getBoundingClientRect. 75 = no slot, never a pass.",
|
||||
"check:panel-sheets": "the shared panel geometry (library/components/poster/panelSheets.js) for posters printed on several sheets: 60x96 = 3 × 60×36 stacked and 66x66 = 2 × 36×66 side by side, 6 in joins, and a single sheet; the sheets tile the finished poster, glue slices tile and bleed slices repeat exactly the join, a bleed join prints a guide not a solid band, a screen preview always shows the red band, and the Bless 5x8ft takes its bands and marks from the module. Two-sided (4 planted defects). Headless.",
|
||||
"check:poster-wall-host-seams": "PosterWall's two host seams, and that with neither set every wall behaves as before. headerAction: default null is the Show-on-map TOGGLE (aria-pressed, one window event per click, is-last, no emit); { labelKey, ariaKey } is a plain button that emits header-action with the item, no window event, no aria-pressed, and reads the label alone for an item with no count. previewSrc: default null is the stored previews/<id>.webp and no IntersectionObserver; set, a card renders only once in view of the wall's own scroller, one render at a time, cached per page, and null / a throw / a timeout ends on the preview-unavailable placeholder. Image errors: a default wall retries the tree's own copy before the placeholder; a generated preview goes straight to it. Section heads: a section with people reads \"<n> people groups · <items>\" exactly as before; one with none reads its items alone. Headless: PosterWall compiled as a client component on a recording renderer; real onClick and a recording IntersectionObserver. 16 mutants each FAIL."
|
||||
"check:poster-wall-host-seams": "PosterWall's two host seams, and that with neither set every wall behaves as before. headerAction: default null is the Show-on-map TOGGLE (aria-pressed, one window event per click, is-last, no emit); { labelKey, ariaKey } is a plain button that emits header-action with the item, no window event, no aria-pressed, and reads the label alone for an item with no count. previewSrc: default null is the stored previews/<id>.webp and no IntersectionObserver; set, a card renders only once in view of the wall's own scroller, one render at a time, cached per page, and null / a throw / a timeout ends on the preview-unavailable placeholder. Image errors: a default wall retries the tree's own copy before the placeholder; a generated preview goes straight to it. Section heads: a section with people reads \"<n> people groups · <items>\" exactly as before; one with none reads its items alone. sectionItemCountKey lets a wall count one section in its own words ({ key, n }); unnamed sections keep the default. Headless: PosterWall compiled as a client component on a recording renderer; real onClick and a recording IntersectionObserver. 17 mutants each FAIL."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/android": "^7.6.9",
|
||||
|
|
|
|||
Loading…
Reference in a new issue