Gates the folder move had narrowed or left open: the MenuCheck contract scans the whole app again (it saw only components/); local/layers refuses an import of an entry (main.tsx, the /watch page) from any layer, and reads a backtick import() as the path it is; the /watch contract forbids the shell, the modules and the features by folder (its one path to App had gone stale); chunkGuard's own test gets the vitest focus/skip rules. The root tests' exemption from local/layers is in the gate register, with its three imports. Placement: Plex's chip builder moves into lib beside the feed's, the folder names into features/folders, and three files only one page uses into that page. ui/FilterRail takes its reset from useRail instead of a page id. The channel page reads the feed's view and builds "Show in feed" itself, and the feed's scroll key is the feed's hook, so the shell holds no feed logic. The floating header re-measures when one page's pills replace another's in the same commit (the claim count stayed equal, so only the observer caught it). A Plex view applied and edited now stays across a visit to another page, as the feed's does: pinned by a test. docs/development.md says what a page with a rail adds in lib.
48 lines
2.3 KiB
TypeScript
48 lines
2.3 KiB
TypeScript
import { readdirSync, readFileSync } from "node:fs";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
// The whole app source (shell, modules, features, ui): the checkable menus live in all of them.
|
|
const root = new URL("../../", import.meta.url);
|
|
const sources = () =>
|
|
readdirSync(root, { recursive: true, encoding: "utf8" })
|
|
.filter((f) => f.endsWith(".tsx") && !f.includes(".test."))
|
|
.map((f) => ({ f, src: readFileSync(new URL(f.replace(/\\/g, "/"), root), "utf8") }));
|
|
|
|
/** Checkable menu rows a file renders: a literal `role="menuitemradio"`, and a role CHOSEN by an
|
|
* expression that can come out checkable — FilterSelect's and ActionMenu's rows, every filter select
|
|
* and checkable action menu in the app, which a literal-only count skipped. */
|
|
function checkableRoles(src: string): number {
|
|
const literal = (src.match(/role="menuitem(?:radio|checkbox)"/g) ?? []).length;
|
|
const chosen = [...src.matchAll(/role=\{([^{}]*)\}/g)].filter(([, e]) =>
|
|
/"menuitem(?:radio|checkbox)"/.test(e!),
|
|
).length;
|
|
return literal + chosen;
|
|
}
|
|
|
|
// `ui/MenuCheck` is the ONE drawn check box of a checkable row (S3e3b replaced seven
|
|
// hand-drawn copies), yet FilterSelect and Combobox still painted their own — a 14px box beside the
|
|
// menus' 16px one. The filled-box paint is the tell: it may live in MenuCheck only.
|
|
describe("the drawn check box", () => {
|
|
it("is painted by MenuCheck alone", () => {
|
|
const offenders = sources()
|
|
.filter(({ f }) => !f.endsWith("MenuCheck.tsx"))
|
|
.filter(({ src }) => /border-accent bg-accent text-accent-fg/.test(src))
|
|
.map(({ f }) => f);
|
|
expect(offenders).toEqual([]);
|
|
});
|
|
|
|
// The player's track rows marked the choice only by an accent label — which the
|
|
// highlight's white text wiped — and four more menus ticked it their own way (a trailing tick, a
|
|
// dot, a filled row). A file with a checkable menu row draws MenuCheck, and no tick of its own.
|
|
it("marks every checkable menu row, with no tick of its own beside it", () => {
|
|
const offenders = sources()
|
|
.filter(({ src }) => {
|
|
const checkable = checkableRoles(src);
|
|
const drawn = (src.match(/<MenuCheck\b/g) ?? []).length;
|
|
return checkable > drawn || (checkable > 0 && /<Check\b/.test(src));
|
|
})
|
|
.map(({ f }) => f);
|
|
expect(offenders).toEqual([]);
|
|
});
|
|
});
|