mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-14 16:45:53 +00:00
Adds an anti-drift coverage selftest that fails CI if a future analytics tab is added to `public/analytics.js` but not registered in `test-a11y-axe-1668.js` `ROUTES`. Wires it into the `.github/workflows/deploy.yml` axe job alongside the existing reciprocity selftest. ## Relationship to #1706 / #1711 Follow-up to #1706 / #1711 — #1711 already added the 8 missing analytics tabs to `ROUTES` (`subpaths`, `nodes`, `distance`, `neighbor-graph`, `rf-health`, `clock-health`, `scopes`, `prefix-tool`). This PR locks that in: a future tab added to `analytics.js` without a corresponding `ROUTES` entry now breaks CI on this assertion. NOT closing #1706 — that issue is already closed by #1711. ## What the gate does (headline) `test-a11y-axe-routes-coverage.js` scrapes `<button class="tab-btn" data-tab="...">` declarations from `public/analytics.js` and asserts every declared tab is exercised by `test-a11y-axe-1668.js` `ROUTES` as `/analytics?tab=<tab>`. Asymmetry vs the existing selftest, intentional: - `test-a11y-axe-1668-selftest.js` — checks `REGISTERED_ANALYTICS_TABS ⊆ analytics.js` dispatch arms (no dead registrations). - `test-a11y-axe-routes-coverage.js` (new) — checks `analytics.js data-tab buttons ⊆ ROUTES` (no axe-blind tabs). Together they keep the axe matrix honest in both directions. ## Diff scope Only two files vs merge base: - `.github/workflows/deploy.yml` (+1 line — wires the new test into the deploy-job batch) - `test-a11y-axe-routes-coverage.js` (+74 lines, new file) No production code changes, no `ROUTES` changes (those landed in #1711). ## TDD framing Net-new drift gate — no prior assertions to break, no behavior change in shipped UI. Per workspace `AGENTS.md` net-new-test exemption ("net-new UI surfaces … test must land in the SAME PR but doesn't need to be the FIRST commit"), this analogous net-new gate ships green from commit 1. A red→green pair would require a synthetic regression in `analytics.js` or `ROUTES`, which isn't appropriate for an anti-drift guard. ## Local run Local Alpine chromium 136 crashes under Playwright's CDP probe (`posix_fallocate64: symbol not found`) — affects the axe runner generally, not this selftest. The coverage assertion itself is pure Node (file read + regex + set diff) and runs locally clean. Real verdict comes from CI's Playwright-bundled chromium. --------- Co-authored-by: Kpa-clawbot <bot@openclaw.local>
This commit is contained in:
co-authored by
Kpa-clawbot
parent
9b8b613832
commit
cbe6e94b1a
@@ -153,6 +153,7 @@ jobs:
|
||||
node test-issue-1668-m4-per-route.js
|
||||
node test-a11y-axe-1668-selftest.js
|
||||
node test-issue-1705-subpath-contrast.js
|
||||
node test-a11y-axe-routes-coverage.js
|
||||
|
||||
- name: 🛡️ Preflight XSS gate — actual --diff check (PR only)
|
||||
# The fixture self-test above (test-preflight-xss-gate.js) only
|
||||
|
||||
@@ -67,7 +67,9 @@ const ROUTES = [
|
||||
'/analytics?tab=hashsizes',
|
||||
'/analytics?tab=collisions',
|
||||
'/analytics?tab=roles',
|
||||
// #1706: expand axe coverage to the remaining 7 analytics tabs plus prefix-tool.
|
||||
// #1706: remaining analytics tabs — every `data-tab=` button in
|
||||
// public/analytics.js must be gated. test-a11y-axe-routes-coverage.js
|
||||
// enforces this; do not remove entries without dropping the tab too.
|
||||
'/analytics?tab=subpaths',
|
||||
'/analytics?tab=nodes',
|
||||
'/analytics?tab=distance',
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* test-a11y-axe-routes-coverage.js — Gate for #1706
|
||||
*
|
||||
* Asserts that EVERY analytics tab declared in `public/analytics.js`
|
||||
* (via `<button class="tab-btn" data-tab="...">`) is covered by the
|
||||
* axe-core CI gate's ROUTES list as `/analytics?tab=<tab>`.
|
||||
*
|
||||
* The existing selftest checks REGISTERED_ANALYTICS_TABS ⊆ analytics.js
|
||||
* dispatch arms, but does NOT enforce that ROUTES exercises every tab.
|
||||
* That gap let 7+ tabs ship un-gated (issue #1706). This file closes it:
|
||||
* any future `data-tab=` button added without a matching ROUTES entry
|
||||
* fails CI on this assertion, blocking silent coverage drift.
|
||||
*
|
||||
* Runs in <100ms — no browser, no playwright, pure source grep.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const mod = require('./test-a11y-axe-1668.js');
|
||||
|
||||
const analyticsSrc = fs.readFileSync(
|
||||
path.join(__dirname, 'public', 'analytics.js'),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
// Scrape every `<button class="...tab-btn..." data-tab="X">` from the
|
||||
// analytics tab bar template literal.
|
||||
const TAB_RE = /<button[^>]*\bclass="[^"]*\btab-btn\b[^"]*"[^>]*\bdata-tab="([^"]+)"/g;
|
||||
const declaredTabs = new Set();
|
||||
let m;
|
||||
while ((m = TAB_RE.exec(analyticsSrc)) !== null) {
|
||||
declaredTabs.add(m[1]);
|
||||
}
|
||||
|
||||
assert.ok(
|
||||
declaredTabs.size > 0,
|
||||
'expected at least one data-tab="..." button in public/analytics.js — regex broken?'
|
||||
);
|
||||
|
||||
// Build the set of analytics tabs ROUTES exercises.
|
||||
const routeTabs = new Set();
|
||||
for (const r of mod.ROUTES) {
|
||||
const q = r.split('?')[1];
|
||||
if (!q) continue;
|
||||
const tm = q.match(/(?:^|&)tab=([^&]+)/);
|
||||
if (tm) routeTabs.add(decodeURIComponent(tm[1]));
|
||||
}
|
||||
|
||||
// Every declared tab MUST appear as a ROUTES entry.
|
||||
const missing = [...declaredTabs].filter(t => !routeTabs.has(t)).sort();
|
||||
assert.deepStrictEqual(
|
||||
missing,
|
||||
[],
|
||||
`axe ROUTES missing analytics tabs (issue #1706): ${missing.join(', ')}\n` +
|
||||
`declared in public/analytics.js: ${[...declaredTabs].sort().join(', ')}\n` +
|
||||
`covered by ROUTES: ${[...routeTabs].sort().join(', ')}`
|
||||
);
|
||||
|
||||
// And every analytics route MUST correspond to a real declared tab (no typos).
|
||||
const stale = [...routeTabs].filter(t => !declaredTabs.has(t)).sort();
|
||||
assert.deepStrictEqual(
|
||||
stale,
|
||||
[],
|
||||
`axe ROUTES references analytics tabs not in public/analytics.js: ${stale.join(', ')}`
|
||||
);
|
||||
|
||||
console.log(
|
||||
`PASS: a11y-axe routes coverage — declared=${declaredTabs.size} ` +
|
||||
`covered=${routeTabs.size} (all analytics tabs gated)`
|
||||
);
|
||||
Reference in New Issue
Block a user