From cac937b874379e4ef4411be4595eb36e05cdbf9a Mon Sep 17 00:00:00 2001 From: quality Date: Wed, 7 Oct 2026 21:37:35 -0400 Subject: [PATCH] test: gate HTML tree structure a browser would silently repair MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add scripts/page-structure.test.mjs, a zero-dependency tokenizer/tree gate over every committed HTML page (redirect shortcuts and 404.html included). It parses pages the way the HTML tokenizer does — comments, raw-text script/style/textarea/title bodies, quoted attributes, the spec's optional end tags, foreign content for inline SVG — and reports every repair a browser would make: stray/mismatched/missing end tags, void-element end tags and non-void self-closing, a block inside

, duplicate attributes, raw < or > inside attribute values (which truncates the tag every sibling [^>]* gate reads), required-parent violations, interactive content nested in a/button/label, non-metadata content in , and the html/head/body/main skeleton. Each rule has a fixture self-test. Document the gate in README Checks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: quality --- README.md | 14 + scripts/page-structure.test.mjs | 527 ++++++++++++++++++++++++++++++++ 2 files changed, 541 insertions(+) create mode 100755 scripts/page-structure.test.mjs diff --git a/README.md b/README.md index f42053b..5684565 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,20 @@ red PR job against. their ARIA pair, and every `[data-*]` selector an inline script queries exists in the markup (so a dropped attribute cannot make a carousel silently no-op). Each rule has a fixture self-test. Zero dependencies. +- `node --test scripts/page-structure.test.mjs` — static tree gate over every committed + HTML page (redirect shortcuts and `404.html` included). Browsers never reject malformed + HTML, they repair it silently, so the gate parses each page the way the HTML tokenizer + does (comments, raw-text ``); + assert.deepEqual(toks.map((t) => `${t.type}:${t.name}`), ["start:a", "start:script", "end:script", "end:a"]); + assert.deepEqual(attributes(toks[0].raw), [{ name: "title", value: "x>y" }, { name: "data-q", value: ">" }]); + assert.deepEqual(attributes(``), [ + { name: "disabled", value: null }, { name: "type", value: "text" }, { name: "data-n", value: "" }, + ]); +}); + +test("fixture: unclosed comment, unterminated tag and unclosed