diff --git a/internal/check-composite-embed-knobs.mjs b/internal/check-composite-embed-knobs.mjs index 09d68150b..d984edaef 100644 --- a/internal/check-composite-embed-knobs.mjs +++ b/internal/check-composite-embed-knobs.mjs @@ -42,7 +42,8 @@ * naming the composite and the knob; both restored, both pass. Removing the zero-request assertion * from the provider case makes it pass on a build that ignores the prop, which is what it is for. * - * Exit: 0 pass or SKIP (no built tree, no browser, no baked token to test with) · 1 fail. + * Exit: 0 pass (or SKIP: no built tree / no browser) · 1 fail · 75 NOT MEASURED (every case skipped, e.g. no + * token: set TEST_MAPBOX_TOKEN). */ import { createServer } from 'node:http' import { spawn } from 'node:child_process' @@ -92,7 +93,10 @@ if (!targets.length) { // The token to hand the partner page is the one the tree already bakes into its own leaves: this // gate must not invent a key, and a token that is not this tree's would 401 and look like our bug. -const bakedToken = (() => { +// The tree bakes no token since the auto engine (the repo ships none), so every tk case SKIPPED and every +// provider case went UNMEASURED: a hollow PASS (rega, ded4b3c1). A seat that has a real public token passes +// it as TEST_MAPBOX_TOKEN (as rega's checks do); otherwise a baked one is still used if a tree has it. +const bakedToken = process.env.TEST_MAPBOX_TOKEN || (() => { for (const b of manifest.bundles || []) { for (const p of b.profiles || []) { if (!p.isMap || !p.iframeUrl) continue @@ -227,4 +231,9 @@ if (fails.length) { console.error(' on a partner page — the case the published embed snippet is written for.') process.exit(1) } +// A run in which no case measured anything is NOT MEASURED (exit 75), never a PASS. +if (!lines.some((l) => /^\s*ok\b/.test(l))) { + console.log(`\nCOMPOSITE-EMBED-KNOBS: NOT MEASURED — every case skipped or went unmeasured${bakedToken ? '' : ' (no token: set TEST_MAPBOX_TOKEN to a public pk. token)'}.`) + process.exit(75) +} console.log(`\nCOMPOSITE-EMBED-KNOBS: PASS — the host's token and provider reach every nested map.`) diff --git a/plugin-designs/research-dashboard/scripts/check-dashboard-geocoder-provider.mjs b/plugin-designs/research-dashboard/scripts/check-dashboard-geocoder-provider.mjs index 67b15a393..c2775cae6 100644 --- a/plugin-designs/research-dashboard/scripts/check-dashboard-geocoder-provider.mjs +++ b/plugin-designs/research-dashboard/scripts/check-dashboard-geocoder-provider.mjs @@ -29,7 +29,7 @@ * maplibre ZERO requests to api.mapbox.com. * both whatever search bars this leaf renders still produce suggestions — a zero bought by * breaking search is not a fix. A leaf with NO bar now FAILS (it used to pass hollow); - * the default leaf is doxa-research-map/research-map, which mounts one (--leaf to change). + * the default leaf is doxa-simple-map/doxa-simple-map, which mounts one on both engines (--leaf to change). * * Exit: 0 pass or SKIP (no built tree, no browser, no data) · 1 fail. */ @@ -53,7 +53,10 @@ const manifest = JSON.parse(readFileSync(MANIFEST, 'utf8')) // ★ A LEAF THAT MOUNTS THE SEARCH BAR, or the suggestion half proves nothing (2026-09-26: the // research-dashboard leaf renders no bar, so every run passed with "search bars: 0" — a hollow PASS). // Default: the research map, which mounts the library GeocoderComponent. --leaf /. -const leafArg = (() => { const i = process.argv.indexOf('--leaf'); return i >= 0 ? process.argv[i + 1] : 'doxa-research-map/research-map' })() +// doxa-simple-map mounts the library GeocoderComponent under BOTH engines. research-map hides its bar +// under MapLibre on purpose (research-map.vue hasGeocoder), so it cannot test the MapLibre half (rega, +// ded4b3c1: 0 bars on both engines, since the tokenless Mapbox run did not draw either). +const leafArg = (() => { const i = process.argv.indexOf('--leaf'); return i >= 0 ? process.argv[i + 1] : 'doxa-simple-map/doxa-simple-map' })() const [wantBundle, wantProfile] = leafArg.split('/') const bundle = (manifest.bundles || []).find((b) => b.bundle === wantBundle) const profile = bundle && (bundle.profiles || []).find((p) => p.iframeUrl && (!wantProfile || p.profile === wantProfile)) @@ -80,6 +83,14 @@ const lines = [] try { for (const engine of ['maplibre', 'mapbox']) { const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }) + // Mapbox needs a token to draw at all since the repo ships none (the auto engine). Give that run a host + // token: TEST_MAPBOX_TOKEN when the seat has one, else a well-formed FAKE (its tile requests are aborted, + // never sent; the engine still starts and the bar still mounts). The MapLibre run gets NO token. + if (engine === 'mapbox') { + const tk = process.env.TEST_MAPBOX_TOKEN || ['pk', Buffer.from(JSON.stringify({ u: 'geocoder-provider-check' })).toString('base64url'), 'c2ln'].join('.') + await page.addInitScript((t) => { window.MAP_TOKEN = t }, tk) + if (!process.env.TEST_MAPBOX_TOKEN) await page.route((u) => /(^|\.)mapbox\.com$/i.test(u.hostname) && u.searchParams.has('access_token'), (r) => r.abort()) + } const mapboxCalls = [] page.on('request', (r) => { const u = r.url(); if (u.includes('api.mapbox.com')) mapboxCalls.push(u) }) await page.goto(`${LEAF}?provider=${engine}`, { waitUntil: 'domcontentloaded', timeout: 60000 }) diff --git a/plugin-designs/research-map-plus-grid/scripts/check-linked-vocabulary.mjs b/plugin-designs/research-map-plus-grid/scripts/check-linked-vocabulary.mjs index 1bb673bb1..0803530f8 100644 --- a/plugin-designs/research-map-plus-grid/scripts/check-linked-vocabulary.mjs +++ b/plugin-designs/research-map-plus-grid/scripts/check-linked-vocabulary.mjs @@ -99,6 +99,13 @@ try { for (const locale of LOCALES) { for (const engine of PROVIDERS) { const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }) + // ★ LISTEN BEFORE THE PAGE RUNS. The grid reports its total on its FIRST filter pass, which can happen + // before an evaluate() after goto attaches a listener; the gate then read "the grid never reported a + // total" while the grid had reported one (rega, ded4b3c1). Every total is recorded from the start. + await page.addInitScript(() => { + window.__lvTotals = [] + document.addEventListener('groups-filtered', (e) => { window.__lvTotals.push(e.detail?.total ?? null) }, true) + }) await page.goto(`${LEAF}?provider=${engine}&lang=${locale}`, { waitUntil: 'domcontentloaded', timeout: 60000 }) // Everything below runs in ONE evaluate: the totals have to be read from the same live page, and @@ -129,9 +136,10 @@ try { // The grid reports its total on every filter pass. Listening is how a total is read here: the // rendered count line is localized and the DOM shape is not this gate's business. - let total = null - const seen = [] - document.addEventListener('groups-filtered', (e) => { total = e.detail?.total ?? null; seen.push(total) }, true) + // Totals recorded since the page started (addInitScript above), so an early first pass is not missed. + const seen = window.__lvTotals || (window.__lvTotals = []) + let total = seen.length ? seen[seen.length - 1] : null + document.addEventListener('groups-filtered', (e) => { total = e.detail?.total ?? null }, true) // Wait for the first fetch to settle — a total of null means the data never arrived, which is a // SKIP (no network in the sandbox), not a failure of the mapping.