refactor(ui): extract reusable component package
This commit is contained in:
parent
e68eb2efee
commit
78a4e28dd4
85 changed files with 4144 additions and 0 deletions
155
src/storybook.test.ts
Normal file
155
src/storybook.test.ts
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
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 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<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("loads dashboard component styles through the global app stylesheet", () => {
|
||||
const packageStyles = readFileSync(join(packageRoot, "src/styles.css"), "utf8");
|
||||
const dashboardFrame = readFileSync(
|
||||
join(componentsDir, "DashboardFrame.tsx"),
|
||||
"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 = readdirSync(componentsDir)
|
||||
.filter((filename) => filename.endsWith(".tsx") && !filename.endsWith(".test.tsx"))
|
||||
.map((filename) => filename.replace(".tsx", ".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(join(componentsDir, `${component}.tsx`)), `${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(join(componentsDir, `${component}.tsx`), "utf8");
|
||||
|
||||
expect(source).toContain('aria-hidden="true"');
|
||||
}
|
||||
});
|
||||
|
||||
test("does not add deferred form/navigation primitives", () => {
|
||||
for (const component of ["Input", "ToggleGroup", "ScrollArea"]) {
|
||||
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([]);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue