import { existsSync, readdirSync, readFileSync } from "node:fs"; import { join } 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 componentDomains = { foundation: [ "Badge", "Button", "IconButton", "IconGlyph", "ProgressMeter", "Separator", "StatusBadge", "ThemeToggle", ], frames: [ "CornerBracketFrame", "DashboardFrame", "DashboardHeader", "DiagonalStripeField", "FooterCell", "FooterStatusCell", "GridFrame", "ModuleCard", "Panel", "ScanlineField", ], operations: [ "ServiceGroupPanel", "ServicePanel", "ServiceRow", "StatusStrip", "SystemState", "WeatherModule", ], telemetry: [ "LineChart", "SignalTrace", "Sparkline", "TelemetryCard", "TelemetryGrid", "TelemetryStrip", ], } as const; const componentFiles: ReadonlyMap = new Map( Object.entries(componentDomains).flatMap(([domain, components]) => components.map((component) => [ component, join(componentsDir, domain, `${component}.tsx`), ]), ), ); const requiredStoryFiles = [ "Badge.stories.tsx", "Button.stories.tsx", "DashboardFrame.stories.tsx", "DashboardHeader.stories.tsx", "DashboardOnePager.stories.tsx", "CornerBracketFrame.stories.tsx", "DiagonalStripeField.stories.tsx", "FooterCell.stories.tsx", "FooterStatusCell.stories.tsx", "GridFrame.stories.tsx", "IconButton.stories.tsx", "IconGlyph.stories.tsx", "LineChart.stories.tsx", "ModuleCard.stories.tsx", "Panel.stories.tsx", "ProgressMeter.stories.tsx", "Separator.stories.tsx", "ServiceGroupPanel.stories.tsx", "ServicePanel.stories.tsx", "ServiceRow.stories.tsx", "SignalTrace.stories.tsx", "Sparkline.stories.tsx", "StatusBadge.stories.tsx", "StatusStrip.stories.tsx", "SystemState.stories.tsx", "ScanlineField.stories.tsx", "TelemetryCard.stories.tsx", "TelemetryGrid.stories.tsx", "TelemetryStrip.stories.tsx", "ThemeToggle.stories.tsx", "WeatherModule.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 filename of requiredStoryFiles) { expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe(true); } }); 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 = [...componentFiles.keys()].map( (component) => `${component}.stories.tsx`, ); for (const filename of componentStoryFiles) { expect(existsSync(join(storiesDir, filename)), `${filename} is missing`).toBe(true); } }); 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", () => { for (const component of ["Input", "ToggleGroup", "ScrollArea"]) { expect(componentFiles.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 = componentFiles.get(component); expect(componentPath, `${component} is missing from the UI component map`).toBeTypeOf( "string", ); return componentPath as string; }