From 3b2b7057cfb6ba8e7e07aeea0ff3aaa34ca4f525 Mon Sep 17 00:00:00 2001 From: quality Date: Sun, 4 Oct 2026 01:53:35 -0400 Subject: [PATCH 1/2] test: add style.css contract gate for structure, var() definitions and script-toggled classes Adds scripts/style-contract.test.mjs, a zero-dependency static gate picked up by the existing node --test scripts/*.test.mjs glob. For every local stylesheet the pages link and every inline + + + +
+ +`; + +test("fixture: clean CSS and page pass every rule", () => { + assert.deepEqual(cssStructureProblems(CLEAN_CSS), []); + const defs = new Set([...cssCustomPropertyDefinitions(CLEAN_CSS), ...pageCustomPropertyDefinitions(CLEAN_HTML)]); + assert.deepEqual([...defs].sort(), ["--bg", "--font", "--hero-h", "--i", "--n", "--pad"]); + assert.deepEqual(undefinedCustomProperties(CLEAN_CSS, defs), []); + assert.deepEqual(scriptSetProperties(CLEAN_HTML), ["--hero-h"]); + assert.deepEqual(unconsumedScriptProperties(CLEAN_HTML, CLEAN_CSS, "page"), []); + assert.deepEqual(scriptToggledClasses(CLEAN_HTML), ["inline-only", "is-on", "js", "no-js", "state-only"]); + assert.deepEqual(unstyledScriptClasses(CLEAN_HTML, CLEAN_CSS, "page"), []); + assert.deepEqual(cssDataSelectors(CLEAN_CSS), ["data-root"]); + assert.deepEqual(missingCssDataSelectors(CLEAN_CSS, [markupOnly(CLEAN_HTML)]), []); + assert.deepEqual(localStylesheetHrefs(CLEAN_HTML), ["/style.css"]); +}); + +test("fixture: unbalanced braces are reported with the opening line", () => { + assert.deepEqual(cssStructureProblems(`.a {\n color: red;\n.b { margin: 0; }\n`), ['css line 1: unclosed "{" for ".a"']); + assert.deepEqual(cssStructureProblems(`.a { color: red; }\n}\n`), ['css line 2: unexpected "}"']); + assert.deepEqual(cssStructureProblems(`@media (x) {\n .a { color: red; }\n`), ['css line 1: unclosed "{" for "@media (x)"']); +}); + +test("fixture: unterminated comment, string and paren are reported", () => { + assert.deepEqual(cssStructureProblems(`.a { color: red; }\n/* never closed\n.b { }`), ["css line 2: unterminated comment"]); + assert.deepEqual(cssStructureProblems(`.a { content: "oops;\n}`), ["css line 1: unterminated string"]); + assert.deepEqual(cssStructureProblems(`.a { width: calc(1px + (2px); }`), ['css: 1 unclosed "("', 'css line 1: unclosed "{" for ".a"']); +}); + +test("fixture: a declaration without a colon or value is reported in its rule", () => { + assert.deepEqual(cssStructureProblems(`.a {\n color red;\n margin: ;\n padding: 0;\n}`), [ + 'css line 1: malformed declaration "color red" in ".a"', + 'css line 1: malformed declaration "margin:" in ".a"', + ]); + assert.deepEqual(cssStructureProblems(`.a { color: red; }\nstray text;\n.b { }`), ['css line 1: top-level text "stray text" is not a rule or at-statement']); + // Bare text that runs into a "{" is a (strange) selector, not stray text — CSS parses it the same way. + assert.deepEqual(cssStructureProblems(`.a { color: red; }\nstray text\n.b { }`), []); + assert.deepEqual(cssStructureProblems(`{ color: red; }`), ['css line 1: "{" without a selector or at-rule prelude']); +}); + +test("fixture: semicolons and braces inside strings, url() and comments are inert", () => { + const css = `.a { content: ";{}"; background: url(data:x;base64,Zm9v); /* } { ; */ font-family: 'A;B'; }`; + assert.deepEqual(cssStructureProblems(css), []); +}); + +test("fixture: a var() with no definition anywhere is reported once with its first line", () => { + const css = `.a { color: var(--ghost); }\n.b { color: var(--ghost); background: var(--other, red); }`; + assert.deepEqual(undefinedCustomProperties(css, new Set(["--bg"])), [ + "css line 1: var(--ghost) has no fallback and --ghost is defined nowhere (stylesheet, inline style= or script setProperty)", + ]); + assert.deepEqual(undefinedCustomProperties(css, new Set(["--ghost"])), []); +}); + +test("fixture: definitions inside comments or script text do not count", () => { + assert.deepEqual([...cssCustomPropertyDefinitions(`/* --nope: 1; */ .a { --yes: 1; }`)], ["--yes"]); + const html = `
`; + assert.deepEqual([...pageCustomPropertyDefinitions(html)], ["--real"]); +}); + +test("fixture: a property the script sets that CSS never reads is reported", () => { + const html = ``; + assert.deepEqual(unconsumedScriptProperties(html, `.a { height: var(--read, 0); }`, "page"), [ + "page: script sets --measured via setProperty but no var(--measured) reads it in CSS", + ]); +}); + +test("fixture: a class the script toggles with no CSS rule and no script read is reported", () => { + const html = ``; + assert.deepEqual(scriptToggledClasses(html), ["gone", "js-only", "orphan", "styled"]); + assert.deepEqual(unstyledScriptClasses(html, `.styled { color: red; } .x.gone { display: none; }`, "page"), [ + 'page: inline script toggles class "orphan" but no CSS rule or script selector references .orphan', + ]); +}); + +test("fixture: toggle's force argument and non-identifier literals are not class names", () => { + const html = ``; + assert.deepEqual(scriptToggledClasses(html), ["is-over"]); +}); + +test("fixture: class selectors are not confused with decimals, file extensions or strings", () => { + const css = `.real { width: .5rem; transition: all .55s; background: url(img/logo.png); content: ".fake"; } .also-real { }`; + assert.deepEqual([...cssClassSelectors(css)].sort(), ["also-real", "real"]); +}); + +test("fixture: an inline `; + assert.deepEqual(unstyledScriptClasses(html, ``, "page"), []); +}); + +test("fixture: a CSS data-* selector that no page's markup carries is reported", () => { + const css = `[data-live] { } .x[data-dead="1"] { }`; + const markup = markupOnly(``); + assert.deepEqual(missingCssDataSelectors(css, [markup]), ["css: selector [data-dead] matches no element in any committed page"]); +}); + +test("fixture: only local stylesheet hrefs are collected", () => { + const html = ` + + + + + +`; + assert.deepEqual(localStylesheetHrefs(html), ["/style.css", "theme.css"]); +}); + +// --------------------------------------------------------------------------- +// Live gate over the committed site. +// --------------------------------------------------------------------------- + +const pages = htmlFiles().map((path) => ({ path, rel: relative(ROOT, path), html: readFileSync(path, "utf8") })); +const contentPages = pages.filter((p) => !isRedirectPage(p.html)); + +// Every local stylesheet any page links, resolved against the checkout. +const stylesheets = new Map(); +for (const p of pages) { + for (const href of localStylesheetHrefs(p.html)) { + const file = href.startsWith("/") ? join(ROOT, href) : resolve(dirname(p.path), href); + const rel = relative(ROOT, file); + if (!stylesheets.has(rel)) stylesheets.set(rel, { file, pages: [] }); + stylesheets.get(rel).pages.push(p.rel); + } +} + +test("the site has committed pages and a linked local stylesheet to check", () => { + assert.ok(contentPages.some((p) => p.rel === "index.html")); + assert.ok(stylesheets.has("style.css"), `expected pages to link /style.css; found ${[...stylesheets.keys()].join(", ") || "none"}`); +}); + +for (const [rel, { file, pages: linkedBy }] of stylesheets) { + test(`${rel}: linked by ${linkedBy.join(", ")} and present in the checkout`, () => { + assert.ok(existsSync(file), `${rel} is linked but not committed`); + }); +} + +const allMarkup = pages.map((p) => markupOnly(p.html)); +const pageDefinitions = new Set(); +for (const p of pages) for (const d of pageCustomPropertyDefinitions(p.html)) pageDefinitions.add(d); + +for (const [rel, { file, pages: linkedBy }] of stylesheets) { + if (!existsSync(file)) continue; + const css = readFileSync(file, "utf8"); + const inlineDefs = new Set(); + for (const p of pages) for (const s of inlineStyles(p.html)) for (const d of cssCustomPropertyDefinitions(s.css)) inlineDefs.add(d); + const definitions = new Set([...cssCustomPropertyDefinitions(css), ...pageDefinitions, ...inlineDefs]); + + test(`${rel}: comments, strings, braces and parens balance and every declaration is name: value`, () => { + assert.deepEqual(cssStructureProblems(css, rel), []); + }); + + test(`${rel}: every var(--x) without a fallback names a custom property defined somewhere`, () => { + assert.deepEqual(undefinedCustomProperties(css, definitions, rel), []); + }); + + test(`${rel}: every [data-*] selector matches an element in some committed page`, () => { + assert.deepEqual(missingCssDataSelectors(css, allMarkup, rel), []); + }); + + for (const p of contentPages.filter((cp) => linkedBy.includes(cp.rel))) { + test(`${p.rel}: every class its inline scripts toggle is styled by ${rel} or read back by a script`, () => { + assert.deepEqual(unstyledScriptClasses(p.html, css, p.rel), []); + }); + + test(`${p.rel}: every custom property its inline scripts set is read by ${rel}`, () => { + assert.deepEqual(unconsumedScriptProperties(p.html, css, p.rel), []); + }); + } +} + +for (const p of pages) { + for (const s of inlineStyles(p.html)) { + test(`${p.rel}: inline