import { describe, expect, it } from "vitest"; import { compileGlobalGovernanceFacts, FactIndex, g, makeTailwindClassFact, makeUsageNodeFact, ruleTailwindArbitraryColor, runRules, } from "../index.js"; import type { TailwindValue } from "../index.js"; const FILE = "components/card.tsx"; function indexWithColorPolicy(): FactIndex { const ix = new FactIndex(); ix.addMany( compileGlobalGovernanceFacts({ styles: [ g.styles.rawColors().forbid({ except: ["transparent", "currentColor"], prefer: "token", severity: "warn", }), ], }), ); return ix; } function addClass(ix: FactIndex, input: { raw: string; utility: string; value: TailwindValue; }) { const node = makeUsageNodeFact({ file: FILE, nodePath: "0:0", element: "div", location: { file: FILE, line: 4, column: 9 }, }); ix.add(node); ix.add( makeTailwindClassFact({ file: FILE, nodeId: node.id, raw: input.raw, originPath: `attribute#0/class#${input.raw}`, prefix: null, modifiers: [], important: false, negative: false, utility: input.utility, value: input.value, location: { file: FILE, line: 4, column: 18 }, }), ); } describe("tailwind/arbitrary-color", () => { it("flags arbitrary hex colors — ACCEPTANCE §3.19", () => { const ix = indexWithColorPolicy(); addClass(ix, { raw: "bg-[#abc123]", utility: "bg", value: { kind: "arbitrary", raw: "#abc123" }, }); const findings = ruleTailwindArbitraryColor(ix); expect(findings).toHaveLength(1); expect(findings[0]).toMatchObject({ ruleId: "tailwind/arbitrary-color", severity: "moderate", attributes: { rawClass: "bg-[#abc123]", rawValue: "#abc123", color: "#abc123", utility: "bg", source: "tailwind", }, }); expect(findings[0].location).toMatchObject({ file: FILE, line: 4 }); }); it("flags arbitrary rgb() colors — ACCEPTANCE §3.20", () => { const ix = indexWithColorPolicy(); addClass(ix, { raw: "text-[rgb(10,20,30)]", utility: "text", value: { kind: "arbitrary", raw: "rgb(10,20,30)" }, }); const findings = ruleTailwindArbitraryColor(ix); expect(findings).toHaveLength(1); expect(findings[0].attributes).toMatchObject({ rawValue: "rgb(10,20,30)", color: "rgb(10,20,30)", }); }); it("does not fire for policy exemptions or non-color utilities", () => { const ix = indexWithColorPolicy(); addClass(ix, { raw: "bg-[transparent]", utility: "bg", value: { kind: "arbitrary", raw: "transparent" }, }); addClass(ix, { raw: "m-[#abc123]", utility: "m", value: { kind: "arbitrary", raw: "#abc123" }, }); expect(ruleTailwindArbitraryColor(ix)).toHaveLength(0); }); it("is registered in runRules", () => { const ix = indexWithColorPolicy(); addClass(ix, { raw: "fill-[#abc123]", utility: "fill", value: { kind: "arbitrary", raw: "#abc123" }, }); expect(runRules(ix).map((f) => f.ruleId)).toContain("tailwind/arbitrary-color"); }); });