88 lines
3.1 KiB
TypeScript
88 lines
3.1 KiB
TypeScript
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { describe, expect, test } from "vitest";
|
|
|
|
const root = process.cwd();
|
|
const componentsDir = join(root, "src/lib/ui/components");
|
|
const storiesDir = join(root, "src/lib/ui/stories");
|
|
|
|
const requiredStoryFiles = [
|
|
"Badge.stories.svelte",
|
|
"Button.stories.svelte",
|
|
"DashboardFrame.stories.svelte",
|
|
"DashboardHeader.stories.svelte",
|
|
"DashboardOnePager.stories.svelte",
|
|
"FooterCell.stories.svelte",
|
|
"FooterStatusCell.stories.svelte",
|
|
"GridFrame.stories.svelte",
|
|
"IconButton.stories.svelte",
|
|
"IconGlyph.stories.svelte",
|
|
"ModuleCard.stories.svelte",
|
|
"Panel.stories.svelte",
|
|
"ProgressMeter.stories.svelte",
|
|
"Separator.stories.svelte",
|
|
"ServiceGroupPanel.stories.svelte",
|
|
"ServicePanel.stories.svelte",
|
|
"ServiceRow.stories.svelte",
|
|
"Sparkline.stories.svelte",
|
|
"StatusBadge.stories.svelte",
|
|
"StatusStrip.stories.svelte",
|
|
"SystemState.stories.svelte",
|
|
"TelemetryCard.stories.svelte",
|
|
"TelemetryGrid.stories.svelte",
|
|
"TelemetryStrip.stories.svelte",
|
|
"WeatherModule.stories.svelte",
|
|
] 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(root, "package.json"), "utf8")) as {
|
|
scripts?: Record<string, string>;
|
|
};
|
|
|
|
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("keeps component and story files paired as the UI inventory changes", () => {
|
|
const componentStoryFiles = readdirSync(componentsDir)
|
|
.filter((filename) => filename.endsWith(".svelte"))
|
|
.map((filename) => filename.replace(".svelte", ".stories.svelte"));
|
|
|
|
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(".svelte") || filename.endsWith(".ts"))
|
|
.map((filename) => readFileSync(join(storiesDir, filename), "utf8").toLowerCase())
|
|
.join("\n");
|
|
|
|
for (const forbidden of forbiddenStoryContent) {
|
|
expect(storyText).not.toContain(forbidden);
|
|
}
|
|
});
|
|
|
|
test("does not add deferred form/navigation primitives", () => {
|
|
for (const component of ["Input", "ToggleGroup", "ScrollArea"]) {
|
|
expect(existsSync(join(root, `src/lib/ui/components/${component}.svelte`))).toBe(false);
|
|
expect(existsSync(join(storiesDir, `${component}.stories.svelte`))).toBe(false);
|
|
}
|
|
});
|
|
});
|