diff --git a/README.md b/README.md index a9a2791..8bd4419 100644 --- a/README.md +++ b/README.md @@ -204,6 +204,8 @@ red PR job against. Then pins the live tree: every committed content page is already up to date, so the tool and `page-csp.test.mjs` agree on what correct means, and a failure names the regenerate command. Zero dependencies. +- `node --test scripts/inline-scripts.test.mjs` — checks shared inline-script extraction, + unique hook matching, page line numbers and stable V8 source URLs. - `node --test scripts/coverage-floor.test.mjs` — branch-coverage floor over the `scripts/*.mjs` helpers the other gates import (`page-csp.mjs`, `html-pages.mjs`, `actions-if.mjs`, `update-csp-hashes.mjs`, `inline-scripts.mjs`) and over every inline diff --git a/scripts/carousels.test.mjs b/scripts/carousels.test.mjs index 5b3c540..27cc68c 100644 --- a/scripts/carousels.test.mjs +++ b/scripts/carousels.test.mjs @@ -998,3 +998,22 @@ test("projects track inset: an empty computed padding-left counts as 0, not NaN" t.next.dispatch("click"); assert.equal(t.lastScroll().left, SLIDE_PITCH, "would be NaN if the empty padding were not coerced"); }); + +test("hero: keyboard focuses a slide without links and retries unsupported focus options", () => { + const t = bootHero(); + t.slides[0].children[0].focus(); + t.slides[1].children.forEach((el) => el.remove()); + const attempts = []; + const target = t.slides[1]; + target.focus = (options) => { + attempts.push(options); + if (options) throw new TypeError('options unsupported'); + t.doc.activeElement = target; + }; + t.root.dispatch('keydown', { key: 'ArrowRight' }); + assert.equal(t.currentIndex(), 1); + assert.equal(target.tabIndex, -1); + assert.deepEqual(attempts, [{ preventScroll: true }, undefined]); + assert.equal(t.doc.activeElement, target); + assert.equal(t.rotating(), false); +}); diff --git a/scripts/coverage-floor.test.mjs b/scripts/coverage-floor.test.mjs index 73c5fa9..7c5c51f 100644 --- a/scripts/coverage-floor.test.mjs +++ b/scripts/coverage-floor.test.mjs @@ -339,3 +339,44 @@ test("every branch of every scripts/*.mjs helper and every inline page script is ...problemsOf(inline, inlineRuns.runs), ], []); }); + +test("fixture: real inline child coverage maps Function wrappers to original page lines", () => { + const dir = mkdtempSync(join(tmpdir(), "coverage-inline-fixture-")); + try { + mkdirSync(join(dir, "scripts")); + const page = '

Fixture

\n\n'; + writeFileSync(join(dir, "page.html"), page); + const helper = pathToFileURL(join(HERE, "inline-scripts.mjs")).href; + writeFileSync(join(dir, "scripts", "page.test.mjs"), ` +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { inlineScript } from ${JSON.stringify(helper)}; +const { src } = inlineScript(readFileSync("page.html", "utf8"), "page.html", "pick"); +new Function("assert", "flag", "expected", src)(assert, true, "yes"); +new Function("assert", "flag", "expected", "unusedLongParameter", src)(assert, true, "yes"); +`); + const registry = { "page.html": [{ hook: "pick" }] }; + const sources = inlineSources(dir, registry); + const first = collect(dir, suiteFiles(dir)); + assert.equal(first.status, 0, first.stdout + first.stderr); + const { runs, problems } = inlineRunsFromReports(first.reports, sources); + assert.deepEqual(problems, []); + assert.deepEqual([...runs.keys()], ["page.html#pick"]); + assert.deepEqual(problemsOf(sources, runs), [ + 'page.html#pick:5 `: "no"` never executed by the suite (add a test that asserts what this branch decides)', + ]); + assert.deepEqual(problemsOf(sources, new Map()), ['page.html#pick: never loaded by the suite']); + writeFileSync(join(dir, "scripts", "both.test.mjs"), ` +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { inlineScript } from ${JSON.stringify(helper)}; +new Function("assert", "flag", "expected", inlineScript(readFileSync("page.html", "utf8"), "page.html", "pick").src)(assert, false, "no"); +`); + const both = collect(dir, suiteFiles(dir)); + assert.equal(both.status, 0, both.stdout + both.stderr); + assert.deepEqual(problemsOf(sources, inlineRunsFromReports(both.reports, sources).runs), []); + assert.deepEqual([...inlineRunsFromReports([{ result: [{ url: "inline:unknown", functions: [] }, { url: "node:fs" }, { url: pathToFileURL(join(dir, "scripts", "unknown.mjs")).href }] }, {}], sources).runs], []); + } finally { + rmSync(dir, { recursive: true, force: true }); + } +}); diff --git a/scripts/inline-scripts.test.mjs b/scripts/inline-scripts.test.mjs new file mode 100644 index 0000000..4bb46aa --- /dev/null +++ b/scripts/inline-scripts.test.mjs @@ -0,0 +1,12 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { inlineScript, inlineUrl } from "./inline-scripts.mjs"; + +test("inline extraction pins body, page line and stable source URL", () => { + const html = '

Hi

\n'; + assert.equal(inlineScript(html, 'p.html', 'widget').line, 2); + assert.equal(inlineUrl('p.html', 'widget'), 'inline:p.html#widget'); + assert.equal(inlineScript(html, 'p.html', 'widget').src, '\nwidget();\n\n//# sourceURL=inline:p.html#widget\n'); + assert.throws(() => inlineScript(html, 'p.html', 'missing'), /p.html: expected exactly one.*found 0/); + assert.throws(() => inlineScript(html + html, 'p.html', 'widget'), /found 2/); +});