Compare commits

...

2 Commits

Author SHA1 Message Date
Kpa-clawbot 13e2005afe fix(#1706): cover remaining 8 analytics tabs in axe CI gate
Adds subpaths, nodes, distance, neighbor-graph, rf-health, clock-health,
scopes, prefix-tool to ROUTES in test-a11y-axe-1668.js. Total analytics
tabs now gated: 15/15 (matches public/analytics.js data-tab buttons).

Also wires test-a11y-axe-routes-coverage.js into the deploy-job test
batch so the coverage assertion runs alongside the existing selftest.

New axe matrix: 24 routes × 2 themes × 2 viewports = 96 cells.

Fixes #1706
2026-06-13 13:51:25 +00:00
Kpa-clawbot 911495fd39 test(#1706): add red-gate asserting ROUTES covers every analytics tab
Asserts every `data-tab=` button in public/analytics.js is exercised by
the axe gate's ROUTES list. Fails on master HEAD because 8 analytics tabs
(subpaths, nodes, distance, neighbor-graph, rf-health, clock-health,
scopes, prefix-tool) ship without axe coverage.

Next commit adds them to ROUTES so this gate turns green and future tabs
added without coverage break CI.
2026-06-13 13:38:12 +00:00
3 changed files with 86 additions and 0 deletions
+1
View File
@@ -152,6 +152,7 @@ jobs:
node test-issue-1633-hide-1byte-hops.js
node test-issue-1668-m4-per-route.js
node test-a11y-axe-1668-selftest.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
+11
View File
@@ -67,6 +67,17 @@ const ROUTES = [
'/analytics?tab=hashsizes',
'/analytics?tab=collisions',
'/analytics?tab=roles',
// #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',
'/analytics?tab=neighbor-graph',
'/analytics?tab=rf-health',
'/analytics?tab=clock-health',
'/analytics?tab=scopes',
'/analytics?tab=prefix-tool',
'/audio-lab',
];
+74
View File
@@ -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)`
);