import { expect, test } from "bun:test"; import { readFileSync, readdirSync } from "node:fs"; import { join } from "node:path"; import { compileWrnFile } from "@wrnexus/compiler"; import { parse as parseWrnSyntax } from "@wrnexus/syntax"; const root = join(import.meta.dir, ".."); const pagesDir = join(root, "app", "pages"); const detailPagesDir = join(pagesDir, "components"); const reference = JSON.parse( readFileSync( join(root, "node_modules", "@wrnexus", "ui", "component-reference.json"), "utf8", ), ) as { count: number; components: Array<{ name: string; mount: string; category: string; props: Array<{ name: string }>; slots: string[]; events: string[]; }>; }; const manifest = JSON.parse( readFileSync(join(root, "showcase-manifest.json"), "utf8"), ) as { removed: string[]; blocks: Array<{ name: string; slug: string; path: string }>; componentCount: number; categoryCount: number; totalDemoCount: number; components: Array<{ name: string; mount: string; slug: string; category: string; demoCount: number; propCount: number; slots: string[]; events: string[]; profiled: boolean; }>; }; const detailSource = (slug: string) => readFileSync(join(detailPagesDir, `${slug}.wrn`), "utf8"); const interactiveOverlayNames = [ "ContextMenu", "Drawer", "Dropdown", "Modal", "Popover", "Tooltip", ]; function mountCount(source: string, mount: string): number { const tag = new RegExp(`<${mount}(?:\\s|>|/)`, "g"); const legacy = new RegExp(` { const packageJson = JSON.parse( readFileSync(join(root, "package.json"), "utf8"), ) as { devDependencies: Record; }; const styles = readFileSync( join(root, "app", "styles", "global.css"), "utf8", ); expect(packageJson.devDependencies["@iconify/tailwind4"]).toBeTruthy(); expect(packageJson.devDependencies["@iconify-json/lucide"]).toBeTruthy(); expect(styles).toContain('@plugin "@iconify/tailwind4";'); expect(styles).toContain( ".playground-preview-source > .wrn-action:not(.w-full)", ); expect(styles).toContain('[data-ui-component="MetricGrid"]'); expect(styles).toContain("width: 100%;"); }); test("showcase manifest matches the generated UI reference", () => { expect( manifest.componentCount + manifest.blocks.length + manifest.removed.length, ).toBe(reference.count); expect(manifest.removed).toEqual([ "AuthForm", "AuthSplitLayout", "PortalDashboard", "PublicPageShell", ]); expect(manifest.blocks.some((block) => block.name === "Hero")).toBe(true); expect(manifest.blocks.some((block) => block.name === "CTASection")).toBe( true, ); expect(manifest.components).toHaveLength(manifest.componentCount); expect(manifest.totalDemoCount).toBeGreaterThanOrEqual( manifest.componentCount * 2, ); expect( new Set(manifest.components.map((component) => component.slug)).size, ).toBe(manifest.componentCount); for (const component of reference.components.filter( (entry) => !manifest.removed.includes(entry.name) && !manifest.blocks.some((block) => block.name === entry.name), )) { const documented = manifest.components.find( (entry) => entry.name === component.name, ); expect(documented).toBeTruthy(); expect(documented?.mount).toBe(component.mount); expect(documented?.events).toEqual(component.events); expect(documented?.slots).toEqual(component.slots); } }); test("showcase uses a searchable documentation shell", () => { const layout = readFileSync( join(root, "app", "layouts", "showcase.wrn"), "utf8", ); const styles = readFileSync( join(root, "app", "styles", "global.css"), "utf8", ); const runtime = readFileSync(join(root, "public", "playground.js"), "utf8"); expect(layout).toContain("data-docs-directory"); expect(layout).toContain("data-docs-search"); expect(layout.match(/data-docs-component-link/g)).toHaveLength( manifest.componentCount, ); expect(styles).toContain("grid-template-columns: 15rem minmax(0, 1fr)"); expect(runtime).toContain("data-docs-menu-toggle"); expect(runtime).toContain('event.key.toLowerCase() === "k"'); }); test("showcase SSR document layout maps persisted design cookies to html attributes", () => { const source = readFileSync( join(root, "app", "layouts", "document.wrn"), "utf8", ); expect(source).toContain(""); expect(source).toContain('
'); for (const setting of ["style", "palette", "mode", "font", "scale"]) { expect(source).toContain(`data-ui-${setting}`); expect(source).toContain(`cookies['wrn-ui-${setting}']`); } }); test("category pages preview every UI component exactly once", () => { const categoryPages = readdirSync(pagesDir) .filter((file) => manifest.components.some( (component) => `${component.category}.wrn` === file, ), ) .map((file) => readFileSync(join(pagesDir, file), "utf8")) .join("\n"); const invalidPreviews = reference.components .filter((entry) => manifest.components.some((component) => component.name === entry.name), ) .flatMap((component) => { const count = mountCount(categoryPages, component.mount); return count === 1 ? [] : [ { name: component.name, mount: component.mount, category: component.category, count, }, ]; }); expect(invalidPreviews).toEqual([]); }); test("every component has a detail page, playground, and manifest-driven demos", () => { const files = readdirSync(detailPagesDir).filter((entry) => entry.endsWith(".wrn"), ); expect(files).toHaveLength(manifest.componentCount); for (const component of manifest.components) { const source = detailSource(component.slug); expect(source).toContain("data-playground-preview"); expect(source).toContain("data-playground-form"); expect(source).toContain("data-playground-code"); expect(source).toContain('href="#playground"'); expect(source.match(/class="demo-workbench"/g)).toHaveLength( component.demoCount, ); expect(mountCount(source, component.mount)).toBeGreaterThanOrEqual( component.demoCount + 1, ); } }, 20_000); test("playgrounds preserve typed values, safe DOM replacement, themes, and event output", () => { const generator = readFileSync( join(root, "scripts", "generate-showcase.mjs"), "utf8", ); const runtime = readFileSync(join(root, "public", "playground.js"), "utf8"); expect(generator).toContain("JSON.parse("); expect(generator).toContain("Number("); expect(generator).toContain("data-playground-json"); expect(generator).toContain("prop.name}='{${playgroundStateName(prop)}}'"); expect(generator).toContain('data-slot="${escapeAttribute(slot)}"'); const generatorWithoutDataSlots = generator.replaceAll( 'data-slot="${escapeAttribute(slot)}"', "", ); expect(generatorWithoutDataSlots).not.toContain( 'slot="${escapeAttribute(slot)}"', ); expect(runtime).toContain("fetch(url"); expect(runtime).toContain("navigator.clipboard.writeText"); expect(runtime).toContain("preview.replaceChildren"); expect(runtime).not.toContain("preview.innerHTML"); expect(runtime).toContain("__wrnexusHydrateScopes"); expect(runtime).toContain("wrnTheme?.bind?.(document)"); expect(runtime).toContain("bindPlaygroundEvents"); expect(runtime).toContain('attributes.push(`${name}="${field.checked}"`)'); }); test("playground enum controls use public profiles instead of inferred implementation comparisons", () => { const generator = readFileSync( join(root, "scripts", "generate-showcase.mjs"), "utf8", ); expect(generator).toContain( "profileFor(component.name)?.options?.[prop.name]", ); expect(generator).not.toContain("source.matchAll(pattern)"); const pageHeader = manifest.components.find( (component) => component.name === "PageHeader", ); if (pageHeader) { const source = detailSource(pageHeader.slug); expect(source).toContain('name="pg_icon" type="text"'); expect(source).toMatch( /