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 { const [result] = await eslint.lintText(source, { filePath }); return (result?.messages ?? []).filter((m) => m.ruleId === RULE).length; } describe("a hand-rolled button", () => { it("is refused in a feature component, the lab and lib, raw or focus-safe", async () => { const raw = 'export const A = () => ;\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 = () => x;\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 = () => ;\n", `src/components/ui/${file}`), ).toBe(0); // Named like a primitive but outside ui/: still refused. expect( await hits("export const E = () => ;\n", "src/components/Button.tsx"), ).toBe(1); expect( await hits( 'import { Button } from "./ui/Button";\nexport const D = () => ;\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 = () => ;\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 = () =>
x
;\n')).toBe(1); expect(await hits('export const G = () => x;\n')).toBe(1); expect(await hits('export const H = () =>
x
;\n')).toBe(0); expect( await hits( 'import { Pressable } from "./ui/Pressable";\nexport const I = () => x;\n', ), ).toBe(0); }); });