import { describe, expect, it } from "vitest"; import { compileGlobalGovernanceFacts, FactIndex, makeTailwindClassFact, makeTailwindTokenResolvedFact, makeUsageNodeFact, ruleTailwindForbiddenPalette, runRules, } from "../index.js"; const FILE = "src/app.tsx"; const LOC = { file: FILE, line: 1, column: 7 }; function indexWithPolicy(): FactIndex { const ix = new FactIndex(); ix.addMany( compileGlobalGovernanceFacts({ tailwind: { palette: { deny: ["red-*"], }, }, }) ); return ix; } function addResolvedClass( ix: FactIndex, raw = "bg-red-500", token = "red-500", source: "default" | "theme-css" = "default" ): void { const node = makeUsageNodeFact({ file: FILE, nodePath: "0", element: "div", location: LOC, }); ix.add(node); ix.add( makeTailwindClassFact({ file: FILE, nodeId: node.id, raw, originPath: `attribute#0/class#${raw}`, prefix: null, modifiers: [], important: false, negative: false, utility: "bg", value: { kind: "token", token }, location: LOC, }) ); ix.add( makeTailwindTokenResolvedFact({ utility: "bg", token, resolved: { kind: "color", value: "#ef4444", source }, }) ); } describe("tailwind/forbidden-palette", () => { it("flags denied palette tokens — ACCEPTANCE §5.28", () => { const ix = indexWithPolicy(); addResolvedClass(ix); const findings = ruleTailwindForbiddenPalette(ix); expect(findings).toHaveLength(1); expect(findings[0]).toMatchObject({ ruleId: "tailwind/forbidden-palette", severity: "moderate", message: "`bg-red-500` uses the `red-500` palette token, which is forbidden by policy `deny: red-*`.", attributes: { utility: "bg", token: "red-500", matchedPattern: "red-*", source: "default", }, }); }); it("is registered in runRules", () => { const ix = indexWithPolicy(); addResolvedClass(ix); expect(runRules(ix).map((f) => f.ruleId)).toContain("tailwind/forbidden-palette"); }); it("allows project-defined theme tokens even when their names do not match the stock allowlist", () => { const ix = indexWithPolicy(); addResolvedClass(ix, "bg-danger", "danger", "theme-css"); expect(ruleTailwindForbiddenPalette(ix)).toHaveLength(0); }); it("never flags universal color keywords (bg-white/text-black/border-transparent) — BUG 6", () => { // An allow-list policy that white/black would otherwise violate. const ix = new FactIndex(); ix.addMany( compileGlobalGovernanceFacts({ tailwind: { palette: { allow: ["brand-*", "neutral-*"] } }, }) ); for (const token of ["white", "black", "transparent", "current"]) { addResolvedClass(ix, `bg-${token}`, token, "default"); } expect(ruleTailwindForbiddenPalette(ix)).toHaveLength(0); }); });