import { describe, expect, it } from "bun:test"; import { classProblems } from "./classes"; import { extractSource } from "./source"; describe("design source extraction", () => { it("parses nested CSS, shorthands, escapes and Tailwind apply", () => { let result = extractSource( "sample.css", String.raw` /* font-size: 99px; */ @layer components { .card { font: 500 var(--text-sm) / 1.5 sans-serif; &:hover { color: rgb(1 2 3 / 50%); } font\2d size: 18px; @apply hover:text-[19px] rounded-[7px]; } } `, ); expect(result.errors).toEqual([]); expect(result.declarations.map(({ property, value }) => [property, value])).toEqual([ ["font", "500 var(--text-sm) / 1.5 sans-serif"], ["color", "rgb(1 2 3 / 50%)"], ["font-size", "18px"], ]); expect(result.classes[0]?.value).toBe("hover:text-[19px] rounded-[7px]"); expect(result.declarations[0]?.line).toBe(5); expect(result.declarations[0]?.context).toBe("CSS > @layer components > .card"); expect(result.declarations[1]?.context).toBe("CSS > @layer components > .card > &:hover"); }); it("extracts JSX styles with quoted, computed, shorthand and numeric properties", () => { let result = extractSource( "sample.tsx", ` let property = "borderRadius"; let color = "#fff"; let base = { fontSize: 18 }; let style = { ...base, "boxShadow": "0 1px 2px #000", [property]: "4px", color } as const; let view =
; `, ); expect(result.errors).toEqual([]); expect(result.declarations.map(({ property, value }) => [property, value])).toEqual([ ["font-size", "18"], ["box-shadow", "0 1px 2px #000"], ["border-radius", "4px"], ["color", "#fff"], ["font-size", "20px"], ["fill", "red"], ]); }); it("resolves static templates and concatenation in style values", () => { let result = extractSource( "sample.tsx", 'let role = "--text-sm"; let view = ;', ); expect(result.declarations.map(({ value }) => value)).toEqual(["var(--text-sm)", "var(--ink)"]); expect(result.declarations.every(declaration => !declaration.dynamic)).toBe(true); }); it("extracts both conditional style and value branches", () => { let result = extractSource( "sample.tsx", ` let view = ; let other = ; `, ); expect(result.declarations.map(({ value }) => value)).toEqual(["16px", "18px", "red", "0s"]); }); it("does not confuse shadowed identifiers or reassigned bindings with constants", () => { let result = extractSource( "sample.tsx", ` let color = "var(--ink)"; function paint(color: string) { return ; } let size = "var(--text-sm)"; size = getSize(); let view = ; `, ); expect(result.declarations).toHaveLength(2); expect(result.declarations.every(declaration => declaration.dynamic)).toBe(true); }); it("marks unresolved properties, computed keys, bindings and spreads", () => { let result = extractSource( "sample.tsx", ` let view = ; let other = ; `, ); expect(result.declarations.map(({ property }) => property)).toEqual([ "*", "font-size", "*", "*", ]); expect(result.declarations.filter(declaration => declaration.dynamic)).toHaveLength(4); }); it("extracts DOM writes including aliases, bracket notation, cssText and Object.assign", () => { let result = extractSource( "sample.ts", ` let style = element.style; style["fontSize"] = "18px"; element["style"].setProperty("border-radius", "7px"); element.style.cssText = "color: red; box-shadow: 0 0 2px black"; Object.assign(element.style, { transitionDuration: "100ms" }); element.setAttribute("style", "font-size: 19px"); element.setAttribute("fill", "blue"); let setStyle = element.style.setProperty; setStyle("font-size", "21px"); `, ); expect(result.errors).toEqual([]); expect(result.declarations.map(({ property }) => property)).toEqual([ "font-size", "border-radius", "color", "box-shadow", "transition-duration", "font-size", "fill", "font-size", ]); }); it("extracts styles and classes from JSX prop spreads and createElement props", () => { let result = extractSource( "sample.tsx", ` let props = { style: { color: "red" }, className: "text-3xl" }; let view = ; let other = ; React.createElement("p", { style: { fontSize: "18px" } }); `, ); expect(result.declarations.map(({ property }) => property)).toEqual([ "color", "*", "font-size", ]); expect(result.classes.map(({ value }) => value)).toEqual(["text-3xl"]); expect(result.declarations[1]?.dynamic).toBe(true); }); it("resolves static object members but marks mutation as a dynamic boundary", () => { let result = extractSource( "sample.tsx", ` let tokens = { size: "18px" }; let first = ; let styles = { color: "red" }; styles.color = getColor(); let second = ; let third = { fontSize: "16px" }; Object.assign(third, extra); let last = ; `, ); expect(result.declarations[0]?.value).toBe("18px"); expect( result.declarations.filter(declaration => declaration.context.startsWith("mutated style object") ), ).toHaveLength(2); }); it("marks mutated class maps and JSX prop objects as dynamic boundaries", () => { let result = extractSource( "sample.tsx", ` let choices = {}; choices["bg-red-500"] = true; let view = ; let props = { style: { color: "var(--color-brand)" } }; Object.assign(props, { style: { color: "red" } }); let other = ; `, ); expect(result.classes).toContainEqual({ value: "choices", line: 4, context: "mutated class object > view >", }); expect(result.classes).toContainEqual({ value: "className", line: 1, context: "class > view >
", dynamic: true, }); }); it("parses recognized CSS strings and CSS tags without scanning arbitrary prose", () => { let result = extractSource( "sample.ts", ` let css = ".label { font-size: 18px; }"; let rules = cssTag; let styledValue = css\`.other { color: red; }\`; let note = "Example font-size: 999px"; let config = { fontSize: 900, color: "red" }; // element.style.fontSize = "800px"; /*
*/ `, ); expect(result.errors).toEqual([]); expect(result.declarations.map(({ value }) => value)).toEqual(["18px", "red"]); }); it("parses HTML styles, style elements, entities, SVG attributes and scripts", () => { let result = extractSource( "sample.html", `A
`, ); expect(result.errors).toEqual([]); expect(result.declarations.map(({ property, value }) => [property, value])).toEqual([ ["font-size", "18px"], ["color", "#fff"], ["font-size", "20px"], ["fill", "red"], ["border-radius", "7px"], ]); expect(result.classes).toEqual([{ value: "text-[19px]", line: 3, context: "HTML >" }]); }); it("reports malformed CSS, JavaScript, HTML and embedded CSS", () => { for ( let [file, source] of [ ["sample.css", ".label { font-size:"], ["sample.tsx", "let view =
"], ["sample.html", '