Files
siftlode/frontend/src/lib/buttonPrimitive.contract.test.ts
T
peter 5ba5679239 test: tests that assumed an idle machine wait for what they assert, or carry a budget for their work
Under a busy machine (the gate's lanes side by side, 12 busy cores) these went red at random: a press landed while the button was still busy, an answer was settled before it was asked for, a page grew past one wait's budget, a lazy chunk was transformed inside a 1 s wait, focus was taken a frame after the press. They now wait for the condition they depend on, preload the chunks they open, and the tests that do heavy work (whole-repo scans, the type-aware linter, module resets) carry an explicit budget naming that work. The whole suite four times under 12 busy-loop processes: red 4/4 before, 0/4 after.
2026-10-17 01:08:15 +02:00

94 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it } from "vitest";
import { eslint, uiFiles } from "./lintProbe.testkit";
// `local/button-primitive` (eslint.config.js): one primitive per button family (docs/DESIGN.md).
// 268 hand-rolled buttons were swept onto ui/Button, IconButton, Pressable, Chip and
// DragHandle; this pins that the rule keeping a new one out actually fires, and where it stands down.
const RULE = "local/button-primitive";
async function hits(source: string, filePath = "src/components/__probe.tsx"): Promise<number> {
const [result] = await eslint.lintText(source, { filePath });
return (result?.messages ?? []).filter((m) => m.ruleId === RULE).length;
}
describe("a hand-rolled button", () => {
// Each new path's first lint makes the type-aware project service load it (0.4–1 s apiece, a repeat
// ~0): this case lints four, ~1.4 s idle, past 5 s on a busy machine.
it(
"is refused in a feature component, the lab and lib, raw or focus-safe",
{ timeout: 30_000 },
async () => {
const raw = 'export const A = () => <button className="px-3">x</button>;\n';
expect(await hits(raw)).toBe(1);
expect(await hits(raw, "src/design-lab/__probe.tsx")).toBe(1);
expect(await hits(raw, "src/lib/__probe.tsx")).toBe(1);
// …and in a ui/ COMPOSITE: a pager or a side panel is built from the primitives too.
expect(await hits(raw, "src/components/ui/Pager.tsx")).toBe(1);
expect(
await hits(
'import { FocusSafeButton } from "./ui/FocusSafeButton";\nexport const B = () => <FocusSafeButton>x</FocusSafeButton>;\n',
),
).toBe(1);
},
);
it("is allowed only in a control primitive, and the primitives pass everywhere", async () => {
for (const file of ["IconButton.tsx", "SegmentedToggle.tsx", "AnchoredPopover.tsx"])
expect(
await hits("export const C = () => <button>x</button>;\n", `src/components/ui/${file}`),
).toBe(0);
// Named like a primitive but outside ui/: still refused.
expect(
await hits("export const E = () => <button>x</button>;\n", "src/components/Button.tsx"),
).toBe(1);
expect(
await hits(
'import { Button } from "./ui/Button";\nexport const D = () => <Button>x</Button>;\n',
),
).toBe(0);
});
// The primitive list lives in the rule body, where neither the gate snapshot nor docs/gates.md
// sees it, so this holds it to exactly these files of ui/.
it("is allowed in exactly the control primitives of ui/", { timeout: 60_000 }, async () => {
const allowed: string[] = [];
for (const file of uiFiles())
if (
(await hits(
'export const R = () => <button type="button">x</button>;\n',
`src/components/ui/${file}`,
)) === 0
)
allowed.push(file);
expect(allowed).toEqual([
"AnchoredPopover.tsx",
"Button.tsx",
"Chip.tsx",
"Combobox.tsx",
"DragHandle.tsx",
"FilterSelect.tsx",
"FocusSafeButton.tsx",
"IconButton.tsx",
"Pressable.tsx",
"PriorityStepper.tsx",
"SegmentedToggle.tsx",
"SelectTrigger.tsx",
"SortControl.tsx",
"Tabs.tsx",
"form.tsx",
]);
});
it("is refused spelled as an element with the button role; a component's role is its own", async () => {
expect(await hits('export const F = () => <div role="button" tabIndex={0}>x</div>;\n')).toBe(1);
expect(await hits('export const G = () => <span role="button">x</span>;\n')).toBe(1);
expect(await hits('export const H = () => <div role="group">x</div>;\n')).toBe(0);
expect(
await hits(
'import { Pressable } from "./ui/Pressable";\nexport const I = () => <Pressable role="button">x</Pressable>;\n',
),
).toBe(0);
});
});