- `overflow-clip` gets a safelist anchor in index.css (its only occurrence is a `.ts` constant), and the chip-lane contract keeps it there. - PlexPlayer's Backspace ownership rule is pinned on all three branches: nothing above, a foreign layer above (it defers), and its own panel open under a layer (it still closes the panel). - Removing a playlist item bumps the list's `updated_at`, like the three other item-only writes. - Seen in a real browser on localdev: an exhausted comfort-shelf tile (an empty pinned playlist) is focusable, aria-disabled, and its Shift+F10 menu offers Play off and Remove from shelf.
101 lines
5.1 KiB
TypeScript
101 lines
5.1 KiB
TypeScript
import { readdirSync, readFileSync } from "node:fs";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { chipLaneClass } from "./useChipOverflowFit";
|
|
|
|
// E10 S4. The chip lanes' hidden measurer is laid out UNBOUNDED on purpose (it exists to read natural
|
|
// widths), and an absolutely-positioned box still feeds its ancestors' SCROLLABLE overflow even while
|
|
// `visibility: hidden`. Un-clipped, that leaked to <html>: at a 1024px viewport the channel manager's
|
|
// document scrolled 249px sideways and the fixed header slid off. `chipLaneClass` carries the clip that
|
|
// contains it — so the invariant worth locking is not "this lane looks right" but "every mirror lives
|
|
// inside a lane that clips". A fifth lane hand-rolling `relative` is exactly the drift this catches,
|
|
// and no DOM test can: jsdom has no layout, so the overflow it guards against is unobservable there.
|
|
//
|
|
// COUNTED per occurrence, not tested per file. The first cut asked `src.includes(chipLaneClass)`, which
|
|
// a file could satisfy with ONE compliant lane while hand-rolling a second — and ChannelTags already
|
|
// holds two lanes and two mirrors, so the likeliest place for a fifth lane was the one file the check
|
|
// was blind inside (E10 review round 1).
|
|
const MIRROR = "invisible absolute left-0 top-0";
|
|
// A mirror only exists to be measured, so the hook is the other half of the signature. Discovering by
|
|
// BOTH means a mirror written with the utilities in another order is still caught by its import.
|
|
const HOOK = "useChipOverflowFit";
|
|
|
|
function count(haystack: string, needle: string): number {
|
|
return haystack.split(needle).length - 1;
|
|
}
|
|
|
|
/** Occurrences of `chipLaneClass` that are actually a LANE, i.e. not the import that brings it in.
|
|
* Counting raw occurrences gave every file one free mirror — the import — so a second, unprotected
|
|
* lane in a file that already had a compliant one still passed, which is the exact hole this test
|
|
* was rewritten to close (E10 review round 2, caught by mutation). */
|
|
function laneCount(src: string): number {
|
|
// `[^;]` rather than `[\s\S]`: an import is stripped up to ITS OWN `from "…";`. The lazy any-char
|
|
// form ran past a side-effect import (`import "./x.css";` has no `from`) into the next statement's
|
|
// `from`, swallowing whatever lay between — a lane there would be uncounted.
|
|
return count(src.replace(/^\s*import\s[^;]*?\sfrom\s+"[^"]*";$/gm, ""), "chipLaneClass");
|
|
}
|
|
|
|
/** Every source file under src/, not just src/components/ — App.tsx and main.tsx sit above it. */
|
|
function sources(): Array<[string, string]> {
|
|
const walk = (u: URL, prefix: string): Array<[string, string]> =>
|
|
readdirSync(u, { withFileTypes: true }).flatMap((e) => {
|
|
if (e.isDirectory()) return walk(new URL(`${e.name}/`, u), `${prefix}${e.name}/`);
|
|
if (!/\.tsx?$/.test(e.name) || e.name.includes(".test.")) return [];
|
|
return [
|
|
[`${prefix}${e.name}`, readFileSync(new URL(e.name, u), "utf-8")] as [string, string],
|
|
];
|
|
});
|
|
return walk(new URL("../", import.meta.url), "");
|
|
}
|
|
|
|
/** Files that render a measurer, by either half of its signature. */
|
|
function withMirror(): Array<[string, string]> {
|
|
return sources().filter(
|
|
([name, src]) =>
|
|
!name.startsWith("lib/useChipOverflowFit") && (src.includes(MIRROR) || src.includes(HOOK)),
|
|
);
|
|
}
|
|
|
|
describe("chip lane containment contract", () => {
|
|
it("strips imports without swallowing what follows a side-effect import", () => {
|
|
const src = [
|
|
'import "./x.css";',
|
|
"const lane = chipLaneClass;",
|
|
'import { chipLaneClass } from "./chipLane";',
|
|
].join("\n");
|
|
expect(laneCount(src)).toBe(1);
|
|
});
|
|
|
|
it("keeps all three halves of the lane class — offsetParent, clip, and room for the focus ring", () => {
|
|
expect(chipLaneClass.split(/\s+/)).toEqual(
|
|
expect.arrayContaining(["relative", "overflow-clip", "[overflow-clip-margin:0.25rem]"]),
|
|
);
|
|
});
|
|
|
|
it("gives every hidden chip measurer its own chipLaneClass lane", () => {
|
|
const offenders = withMirror()
|
|
.map(([name, src]) => ({
|
|
name,
|
|
mirrors: count(src, MIRROR),
|
|
lanes: laneCount(src),
|
|
}))
|
|
.filter((f) => f.lanes < f.mirrors);
|
|
expect(offenders).toEqual([]);
|
|
});
|
|
|
|
it("finds the measurers it claims to guard (the marker has not been renamed away)", () => {
|
|
// The MIRROR alone, not the hook: `lib/chipOverflow.ts` names the hook in a prose comment and was
|
|
// padding this canary, so it stayed satisfied with one real measurer fewer (round 2).
|
|
expect(sources().filter(([, src]) => src.includes(MIRROR)).length).toBeGreaterThanOrEqual(3);
|
|
});
|
|
|
|
// The clip is a Tailwind utility whose only occurrence is `chipLaneClass`, a `.ts` constant: if
|
|
// source detection ever stopped covering that file, the utility would leave the bundle and the
|
|
// leak above would return with every lane green. index.css anchors it; this keeps the anchor.
|
|
it("keeps the clip in the bundle whatever source detection sees", () => {
|
|
const css = readFileSync(new URL("../index.css", import.meta.url), "utf-8");
|
|
expect(css).toMatch(/@source inline\("[^"]*\boverflow-clip\b[^"]*"\)/);
|
|
});
|
|
});
|