- The raw-source contract tests read files from disk (rawSources.testkit) instead of import.meta.glob, which knip counted as importing every src file — its unused-files lane could never name an orphan. knip now names src/watchMain.tsx as an entry (registered in docs/gates.md). - vite build fails when index.html or watch.html is not emitted. - The watch-entry import walk follows dynamic import() too. - audit_metrics.py's plan-code patterns are held equal to the guard's. - CrashBoundary is domain-free: it moves into components/ui.
55 lines
2.3 KiB
TypeScript
55 lines
2.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { readSources } from "./rawSources.testkit";
|
|
|
|
// Every push that opens a new view over the current one remembers the list item it was opened from
|
|
// (`lib/history.rememberNavPosition`), so Back hands focus back to that item. A push that
|
|
// forgets strands keyboard focus on <body> again with no test noticing — the flow only shows in a real
|
|
// browser, after a round trip. So the source is the check: each `history.pushState(` in the app has a
|
|
// `rememberNavPosition(` right before it or inside its arguments. The overlay stack's own push is the
|
|
// one exception — an overlay entry opens no view, and its list stays mounted underneath.
|
|
//
|
|
// The assertion pins the SET of offending sites and checks the scan found pushes at all: a guard whose
|
|
// glob stops matching is green over nothing.
|
|
|
|
const files = readSources(__dirname, ["../**/*.ts", "../**/*.tsx", "!../**/*.test.*"]) as Record<
|
|
string,
|
|
string
|
|
>;
|
|
|
|
const EXEMPT: Record<string, string> = {
|
|
"lib/history.ts#_ov": "the overlay stack: an overlay entry opens no view over the page",
|
|
};
|
|
|
|
// Lines before a push, and lines of the push call itself, that may carry the remember call.
|
|
const BEFORE = 4;
|
|
const WITHIN = 4;
|
|
|
|
function offenders(): { hits: string[]; pushes: number } {
|
|
const hits: string[] = [];
|
|
let pushes = 0;
|
|
for (const [path, code] of Object.entries(files)) {
|
|
// Glob keys are relative to this file: `./x.ts` is lib, `../components/x.tsx` is a sibling folder.
|
|
const rel = path.startsWith("./") ? `lib/${path.slice(2)}` : path.replace(/^\.\.\//, "");
|
|
const lines = code.split("\n");
|
|
lines.forEach((line, i) => {
|
|
if (!line.includes("history.pushState(")) return;
|
|
pushes++;
|
|
const around = lines.slice(Math.max(0, i - BEFORE), i + WITHIN).join("\n");
|
|
if (around.includes("rememberNavPosition(")) return;
|
|
const exempt = Object.keys(EXEMPT).find(
|
|
(id) => id.startsWith(`${rel}#`) && around.includes(id.split("#")[1]!),
|
|
);
|
|
if (!exempt) hits.push(`${rel}:${i + 1}`);
|
|
});
|
|
}
|
|
return { hits: hits.sort(), pushes };
|
|
}
|
|
|
|
describe("every view push remembers the item it was opened from", () => {
|
|
it("finds no pushState without rememberNavPosition", () => {
|
|
const { hits, pushes } = offenders();
|
|
expect(pushes).toBeGreaterThan(4);
|
|
expect(hits).toEqual([]);
|
|
});
|
|
});
|