import { existsSync, readdirSync, readFileSync } from "node:fs"; import { basename, join, relative } from "node:path"; import { describe, expect, test } from "vitest"; const root = process.cwd(); const packageRoot = existsSync(join(root, "packages/ui/package.json")) ? join(root, "packages/ui") : root; const componentsDir = join(packageRoot, "src/components"); const storiesDir = join(packageRoot, "src/stories"); const allowedComponentDomains = [ "foundation", "frames", "operations", "telemetry", ] as const; const compositionStoryFiles = ["DashboardOnePager.stories.tsx"] as const; const forbiddenStoryContent = [ "dimension lab", "dimensionlab", "vince", "homepage", ] as const; describe("Storybook inventory", () => { test("exposes scripts for local and static Storybook review", () => { const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")) as { scripts?: Record; }; expect(packageJson.scripts?.storybook).toBeTypeOf("string"); expect(packageJson.scripts?.storybook).toContain("storybook dev"); expect(packageJson.scripts?.["build-storybook"]).toBe("storybook build"); }); test("has a story for every reusable dashboard UI component", () => { for (const component of componentInventory()) { expect( existsSync(join(storiesDir, component.storyFile)), `${component.storyFile} is missing`, ).toBe(true); } }); test("uses only approved component domain folders", () => { const actualDomains = readdirSync(componentsDir, { withFileTypes: true }) .filter((entry) => entry.isDirectory()) .map((entry) => entry.name) .sort(); expect(actualDomains).toEqual([...allowedComponentDomains].sort()); }); test("loads dashboard component styles through the global app stylesheet", () => { const packageStyles = readFileSync(join(packageRoot, "src/styles.css"), "utf8"); const dashboardFrame = readFileSync(requiredComponentPath("DashboardFrame"), "utf8"); expect(packageStyles).toContain('./components/styles.css'); expect(dashboardFrame).not.toContain('./styles.css'); }); test("configures Storybook theme switching for reusable components", () => { const previewSource = readFileSync(join(packageRoot, ".storybook/preview.ts"), "utf8"); expect(previewSource).toContain("globalTypes"); expect(previewSource).toContain("data-ui-theme"); expect(previewSource).toContain("../src/styles.css"); }); test("keeps component and story files paired as the UI inventory changes", () => { const componentStoryFiles = new Set( componentInventory().map((component) => component.storyFile), ); for (const filename of componentStoryFiles) { expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe( true, ); } const unpairedStoryFiles = storyFiles().filter( (filename) => !componentStoryFiles.has(filename) && !compositionStoryFiles.includes( filename as (typeof compositionStoryFiles)[number], ), ); expect(unpairedStoryFiles).toEqual([]); }); test("exports every reusable component through the package barrel", () => { const indexSource = readFileSync(join(packageRoot, "src/index.ts"), "utf8"); for (const component of componentInventory()) { expect(indexSource).toContain( `export { ${component.name} } from "${component.relativeExportPath}";`, ); } }); test("exposes grouped package entrypoints for each component domain", () => { const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8")) as { exports?: Record; }; const rootIndexSource = readFileSync(join(packageRoot, "src/index.ts"), "utf8"); const componentsByDomain = new Map<(typeof allowedComponentDomains)[number], string[]>(); for (const domain of allowedComponentDomains) { componentsByDomain.set(domain, []); } for (const component of componentInventory()) { componentsByDomain.get(component.domain)?.push(component.name); } for (const domain of allowedComponentDomains) { const domainIndexPath = join(componentsDir, domain, "index.ts"); const domainIndexSource = existsSync(domainIndexPath) ? readFileSync(domainIndexPath, "utf8") : ""; expect(existsSync(domainIndexPath), `${domain} index is missing`).toBe(true); expect(rootIndexSource).toContain(`export * from "./components/${domain}";`); expect(packageJson.exports?.[`./${domain}`]).toMatchObject({ types: `./dist/components/${domain}/index.d.ts`, development: `./src/components/${domain}/index.ts`, default: `./dist/components/${domain}/index.js`, }); for (const componentName of componentsByDomain.get(domain) ?? []) { expect(domainIndexSource).toContain( `export * from "./${componentName}";`, ); } } }); test("exports component prop interfaces through grouped package entrypoints", () => { let propInterfaceCount = 0; for (const component of componentInventory()) { const source = readFileSync(component.path, "utf8"); const propTypeName = `${component.name}Props`; const propExportPattern = new RegExp( `export\\s+(interface|type)\\s+${propTypeName}\\b`, ); if (!propExportPattern.test(source)) { continue; } propInterfaceCount += 1; const domainIndexSource = readFileSync( join(componentsDir, component.domain, "index.ts"), "utf8", ); expect(domainIndexSource).toContain( `export * from "./${component.name}";`, ); } expect(propInterfaceCount).toBeGreaterThan(0); }); test("keeps Storybook fixtures generic and content-free", () => { const storyText = readdirSync(storiesDir) .filter((filename) => filename.endsWith(".tsx") || filename.endsWith(".ts")) .map((filename) => readFileSync(join(storiesDir, filename), "utf8").toLowerCase()) .join("\n"); for (const forbidden of forbiddenStoryContent) { expect(storyText).not.toContain(forbidden); } }); test("includes style-only decorative primitives in the reusable UI inventory", () => { for (const component of [ "CornerBracketFrame", "DiagonalStripeField", "ScanlineField", "SignalTrace", ]) { expect(existsSync(requiredComponentPath(component)), `${component} is missing`).toBe(true); expect( existsSync(join(storiesDir, `${component}.stories.tsx`)), `${component}.stories.tsx is missing`, ).toBe(true); } }); test("keeps decorative primitives hidden from assistive technology", () => { for (const component of [ "CornerBracketFrame", "DiagonalStripeField", "ScanlineField", "SignalTrace", ]) { const source = readFileSync(requiredComponentPath(component), "utf8"); expect(source).toContain('aria-hidden="true"'); } }); test("does not add deferred form/navigation primitives", () => { const componentNames = new Set( componentInventory().map((component) => component.name), ); for (const component of ["Input", "ToggleGroup", "ScrollArea"]) { expect(componentNames.has(component)).toBe(false); expect(existsSync(join(componentsDir, `${component}.tsx`))).toBe(false); expect(existsSync(join(storiesDir, `${component}.stories.tsx`))).toBe(false); } }); test("does not keep legacy stories in the React Storybook inventory", () => { const legacyStoryExtension = [".stories", ".sve", "lte"].join(""); const legacyStories = readdirSync(storiesDir).filter((filename) => filename.endsWith(legacyStoryExtension), ); expect(legacyStories).toEqual([]); }); }); function requiredComponentPath(component: string): string { const componentPath = componentInventory().find( (entry) => entry.name === component, )?.path; expect(componentPath, `${component} is missing from the UI component tree`).toBeTypeOf( "string", ); return componentPath as string; } interface ComponentInventoryItem { domain: (typeof allowedComponentDomains)[number]; name: string; path: string; relativeExportPath: string; storyFile: string; } type WorkspacePackageExport = | string | { types?: string; development?: string; default?: string; }; function componentInventory(): ComponentInventoryItem[] { const components = allowedComponentDomains .flatMap((domain) => collectComponentFiles(join(componentsDir, domain))) .sort((a, b) => a.name.localeCompare(b.name)); const names = components.map((component) => component.name); expect(names).toEqual([...new Set(names)]); return components; } function collectComponentFiles(directory: string): ComponentInventoryItem[] { return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => { const entryPath = join(directory, entry.name); if (entry.isDirectory()) { return collectComponentFiles(entryPath); } if ( !entry.isFile() || !entry.name.endsWith(".tsx") || entry.name.endsWith(".test.tsx") ) { return []; } const name = basename(entry.name, ".tsx"); const relativeComponentPath = relative(componentsDir, entryPath).replace(/\\/g, "/"); const domain = relativeComponentPath.split( "/", )[0] as (typeof allowedComponentDomains)[number]; const relativeExportPath = `./${relative(join(packageRoot, "src"), entryPath) .replace(/\\/g, "/") .replace(/\.tsx$/, "")}`; return [ { domain, name, path: entryPath, relativeExportPath, storyFile: `${name}.stories.tsx`, }, ]; }); } function storyFiles(): string[] { return readdirSync(storiesDir) .filter((filename) => filename.endsWith(".stories.tsx")) .sort(); }