import { describe, expect, test } from "bun:test"; import { readdir, readFile } from "node:fs/promises"; import { join } from "node:path"; import { parse } from "../../compiler/src/index.ts"; const componentsDir = join(import.meta.dir, "..", "components"); const replacementComponents = [ "Container", "Grid", "Columns", "Card", "Link", "Image", "SearchBox", "Map", "List", "Marquee", "Tabs", "Timeline", "Footer", "Typography", "Divider", ] as const; const newComponents = [ "Section", "SectionHeader", "MarketingSectionHeader", "PageHeader", "TextLink", "FeatureGrid", "FeatureCard", "FeatureIconCard", "Hero", "SplitHero", "HeroActions", "AnnouncementBar", "MetricCard", "MetricGrid", "StatsBar", "CTASection", "BackToTop", "PublicPageShell", ] as const; const allComponents = [...replacementComponents, ...newComponents]; function openingTags(source: string): string[] { const tags: string[] = []; let index = 0; while (index < source.length) { const start = source.indexOf("<", index); if (start === -1) break; let cursor = start + 1; let quote: "'" | '"' | null = null; while (cursor < source.length) { const char = source[cursor]; if (quote) { if (char === quote) quote = null; } else if (char === "'" || char === '"') { quote = char; } else if (char === ">") { tags.push(source.slice(start, cursor + 1)); cursor += 1; break; } cursor += 1; } index = cursor; } return tags; } describe("@wrnexus/ui redesign component pack", () => { test("contains every replacement and new component", async () => { const files = new Set(await readdir(componentsDir)); for (const name of allComponents) { expect(files.has(`${name}.wrn`)).toBe(true); } }); for (const name of allComponents) { test(`${name} has a valid component shell`, async () => { const source = await readFile(join(componentsDir, `${name}.wrn`), "utf8"); expect(source).toContain(`component ${name} {`); expect(source).toContain("props {"); expect(source).toContain("view {"); expect(source).toContain(`data-ui-component="${name}"`); expect(source).not.toContain("React"); expect(source).not.toContain("useState"); expect(source).not.toContain(" { for (const name of allComponents) { const source = await readFile(join(componentsDir, `${name}.wrn`), "utf8"); for (const tag of openingTags(source)) { const plainClassAttributes = tag.match(/(? { for (const name of allComponents) { const source = await readFile(join(componentsDir, `${name}.wrn`), "utf8"); expect(source).not.toMatch(/=\\"\{[^\n]*\}\\"/); } }); test("Navbar only displays its utility bar when the topbar slot has rendered content", async () => { const source = await readFile(join(componentsDir, "Navbar.wrn"), "utf8"); expect(source).toContain(".wrn-navbar__topbar:not(:has(> *))"); expect(source).toContain(".wrn-navbar__topbar:has(> *)"); expect(source).not.toContain(".wrn-navbar__topbar:not(:empty)"); }); test("Navbar supports in-flow floating and elevated menu panels", async () => { const source = await readFile(join(componentsDir, "Navbar.wrn"), "utf8"); const ast = parse(source); const defaults = new Map(ast.props.map((prop) => [prop.name, prop.default])); expect(defaults.get("floating")).toBe("false"); expect(defaults.get("startFloating")).toBe('"0px"'); expect(source).toContain("wrn-navbar--floating"); expect(source).toContain("--wrn-navbar-floating-start"); expect(source).toContain("z-index: 1100"); }); test("CTASection exposes responsive section spacing", async () => { const source = await readFile(join(componentsDir, "CTASection.wrn"), "utf8"); const ast = parse(source); const defaults = new Map(ast.props.map((prop) => [prop.name, prop.default])); expect(defaults.get("spacing")).toBe('"none"'); expect(source).toContain("data-spacing='{spacing}'"); expect(source).toContain('.wrn-cta-section[data-spacing="sm"]'); expect(source).toContain('.wrn-cta-section[data-spacing="md"]'); expect(source).toContain('.wrn-cta-section[data-spacing="lg"]'); }); test("existing public defaults remain release compatible", async () => { const expectations: Record = { Container: [ 'size = "default"', 'color = "primary"', "columns = 2", 'gap = "md"', 'maxWidth = "xl"', ], Grid: [ 'size = "default"', 'color = "primary"', "columns = 2", 'gap = "md"', 'maxWidth = "xl"', ], Columns: [ 'size = "default"', 'color = "primary"', "columns = 2", 'gap = "md"', 'maxWidth = "xl"', ], Link: ['label = "Link"', 'href = "#"', "external = false"], Image: ['src = ""', 'alt = ""', 'loading = "lazy"', "rounded = false"], SearchBox: [ 'label = "Search Box"', 'type = "search"', "disabled = false", "required = false", ], Map: ['title = "Map"', "items = []", 'variant = "default"'], List: ['title = "List"', "items = []", 'variant = "default"'], Marquee: ['title = "Marquee"', "items = []", 'variant = "default"'], Tabs: ['label = "Tabs"', "items = []", 'active = ""', 'orientation = "horizontal"'], Timeline: ['title = "Timeline"', "items = []", 'variant = "default"'], Footer: ['label = "Footer navigation"', "items = []", "columns = 3", 'maxWidth = "compact"'], Typography: [ 'size = "default"', 'color = "primary"', "columns = 2", 'gap = "md"', 'maxWidth = "xl"', ], Divider: ['label = ""', 'orientation = "horizontal"'], }; for (const [name, tokens] of Object.entries(expectations)) { const source = await readFile(join(componentsDir, `${name}.wrn`), "utf8"); const ast = parse(source); const defaults = new Map(ast.props.map((prop) => [prop.name, prop.default])); for (const token of tokens) { const match = /^([A-Za-z_$][\w$]*)\s*=\s*([\s\S]+)$/.exec(token); expect(match).not.toBeNull(); expect(defaults.get(match![1]!)).toBe(match![2]!); } } }); });