Cross-check el() ids against index.html in the selftest
A knob wired in app.js but missing from the markup throws during wiring, aborts the module and leaves a blank page - a symptom pointing nowhere near its cause. It has now happened twice, so it gets a check rather than vigilance: the selftest fetches both files and compares the id sets in each direction. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
9a11eeabc1
commit
a146d271b6
3 changed files with 38 additions and 2 deletions
|
|
@ -148,7 +148,11 @@ chromium --headless --virtual-time-budget=8000 --dump-dom \
|
||||||
http://127.0.0.1:8777/selftest.html | grep -oE '(PASS|FAIL) [0-9/]+'
|
http://127.0.0.1:8777/selftest.html | grep -oE '(PASS|FAIL) [0-9/]+'
|
||||||
```
|
```
|
||||||
|
|
||||||
Or open `selftest.html`. 29 assertions over the stages below detection.
|
Or open `selftest.html`. 41 assertions over the stages below detection, plus a
|
||||||
|
wiring cross-check: every `el('id')` in `app.js` must exist in `index.html`. A
|
||||||
|
knob wired in one but not the other throws during wiring, which aborts the rest
|
||||||
|
of the module and leaves a blank page — a symptom that points nowhere near its
|
||||||
|
cause, and which has happened twice.
|
||||||
|
|
||||||
The ring-simplicity check is the load-bearing one. Because `hold` parts *cut*
|
The ring-simplicity check is the load-bearing one. Because `hold` parts *cut*
|
||||||
between poses instead of interpolating, a ring whose vertex order is wrong
|
between poses instead of interpolating, a ring whose vertex order is wrong
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,37 @@ const spreadX = (frames, slot) => {
|
||||||
|
|
||||||
/* ---- the tests ---- */
|
/* ---- the tests ---- */
|
||||||
|
|
||||||
|
// Cross-check every el('id') in app.js against the ids in index.html.
|
||||||
|
//
|
||||||
|
// This bug class has bitten twice: a knob wired in app.js but absent from the
|
||||||
|
// markup throws during wiring, which aborts the rest of the module and leaves a
|
||||||
|
// blank page. The symptom ("nothing happens") points nowhere near the cause, so
|
||||||
|
// it is worth an automated check rather than vigilance.
|
||||||
|
export async function runWiring() {
|
||||||
|
const out = [];
|
||||||
|
try {
|
||||||
|
const [app, html] = await Promise.all([
|
||||||
|
fetch('./js/app.js').then((r) => r.text()),
|
||||||
|
fetch('./index.html').then((r) => r.text()),
|
||||||
|
]);
|
||||||
|
const ids = new Set([...app.matchAll(/\bel\(\s*['"]([\w-]+)['"]\s*\)/g)].map((m) => m[1]));
|
||||||
|
const list = app.match(/for \(const id of \[([\s\S]*?)\]\)/);
|
||||||
|
if (list) {
|
||||||
|
for (const m of list[1].matchAll(/'([\w]+)'/g)) { ids.add(m[1]); ids.add(m[1] + 'v'); }
|
||||||
|
}
|
||||||
|
const have = new Set([...html.matchAll(/id="([\w-]+)"/g)].map((m) => m[1]));
|
||||||
|
const missing = [...ids].filter((i) => !have.has(i));
|
||||||
|
out.push({ name: `every el() id exists in index.html (${ids.size} checked)`,
|
||||||
|
pass: missing.length === 0, detail: missing.join(', ') });
|
||||||
|
const unused = [...have].filter((i) => !ids.has(i));
|
||||||
|
out.push({ name: 'no orphaned ids in index.html', pass: unused.length === 0,
|
||||||
|
detail: unused.join(', ') });
|
||||||
|
} catch (e) {
|
||||||
|
out.push({ name: 'wiring check ran', pass: false, detail: e.message });
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
export function run() {
|
export function run() {
|
||||||
results.length = 0;
|
results.length = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,11 @@
|
||||||
</style></head><body>
|
</style></head><body>
|
||||||
<h1 id="head">running…</h1><ul id="out"></ul>
|
<h1 id="head">running…</h1><ul id="out"></ul>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import { run } from './js/selftest.js';
|
import { run, runWiring } from './js/selftest.js';
|
||||||
let res;
|
let res;
|
||||||
try { res = run(); }
|
try { res = run(); }
|
||||||
catch (e) { res = [{ name: 'harness threw: ' + e.message, pass: false, detail: String(e.stack).split('\n')[1] || '' }]; }
|
catch (e) { res = [{ name: 'harness threw: ' + e.message, pass: false, detail: String(e.stack).split('\n')[1] || '' }]; }
|
||||||
|
res = res.concat(await runWiring());
|
||||||
const pass = res.filter(r => r.pass).length;
|
const pass = res.filter(r => r.pass).length;
|
||||||
const fail = res.length - pass;
|
const fail = res.length - pass;
|
||||||
document.getElementById('head').textContent = `${fail === 0 ? 'PASS' : 'FAIL'} — ${pass}/${res.length} assertions`;
|
document.getElementById('head').textContent = `${fail === 0 ? 'PASS' : 'FAIL'} — ${pass}/${res.length} assertions`;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue