fix(web): align vitest setup with storybook docs
Quentin Gliech
and
Claude Opus 4.6 (1M context)
created 1 month ago
- Restore setupFiles in storybook project (addon detects it, skips
auto-provisioning, but it's still needed per docs)
- Add storybookScript for watch mode DX (story links in failures)
- Switch snapshot tests from render() to run() which goes through
the full Storybook lifecycle (loaders, decorators, play functions)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change summary
webui2/.storybook/vitest.setup.ts | 5
webui2/src/components/ui/__snapshots__/button.test.tsx.snap | 96 ++++--
webui2/src/components/ui/button.test.tsx | 7
webui2/vitest.config.ts | 2
4 files changed, 63 insertions(+), 47 deletions(-)
Detailed changes
@@ -3,9 +3,8 @@ import { beforeAll } from "vitest";
import * as previewAnnotations from "./preview";
-// Apply Storybook decorators/parameters from preview.ts to portable stories.
-// Note: the @storybook/addon-vitest project handles this automatically;
-// this setup file is only used by the snapshot test project.
+// Apply Storybook decorators/parameters from preview.ts to portable stories
+// used by the snapshot test project.
const annotations = setProjectAnnotations([previewAnnotations]);
beforeAll(annotations.beforeAll);
@@ -1,65 +1,81 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Button/Default matches snapshot 1`] = `
-<button
- class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground shadow-sm hover:bg-primary/90 h-9 px-4 py-2"
->
- Button
-</button>
+<div>
+ <button
+ class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground shadow-sm hover:bg-primary/90 h-9 px-4 py-2"
+ >
+ Button
+ </button>
+</div>
`;
exports[`Button/Destructive matches snapshot 1`] = `
-<button
- class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90 h-9 px-4 py-2"
->
- Delete
-</button>
+<div>
+ <button
+ class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90 h-9 px-4 py-2"
+ >
+ Delete
+ </button>
+</div>
`;
exports[`Button/Ghost matches snapshot 1`] = `
-<button
- class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2"
->
- Ghost
-</button>
+<div>
+ <button
+ class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2"
+ >
+ Ghost
+ </button>
+</div>
`;
exports[`Button/Large matches snapshot 1`] = `
-<button
- class="inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground shadow-sm hover:bg-primary/90 h-10 rounded-md px-8"
->
- Large
-</button>
+<div>
+ <button
+ class="inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground shadow-sm hover:bg-primary/90 h-10 rounded-md px-8"
+ >
+ Large
+ </button>
+</div>
`;
exports[`Button/Link matches snapshot 1`] = `
-<button
- class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-primary underline-offset-4 hover:underline h-9 px-4 py-2"
->
- Link
-</button>
+<div>
+ <button
+ class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-primary underline-offset-4 hover:underline h-9 px-4 py-2"
+ >
+ Link
+ </button>
+</div>
`;
exports[`Button/Outline matches snapshot 1`] = `
-<button
- class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2"
->
- Outline
-</button>
+<div>
+ <button
+ class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground h-9 px-4 py-2"
+ >
+ Outline
+ </button>
+</div>
`;
exports[`Button/Secondary matches snapshot 1`] = `
-<button
- class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80 h-9 px-4 py-2"
->
- Secondary
-</button>
+<div>
+ <button
+ class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80 h-9 px-4 py-2"
+ >
+ Secondary
+ </button>
+</div>
`;
exports[`Button/Small matches snapshot 1`] = `
-<button
- class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground shadow-sm hover:bg-primary/90 h-8 rounded-md px-3 text-xs"
->
- Small
-</button>
+<div>
+ <button
+ class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 bg-primary text-primary-foreground shadow-sm hover:bg-primary/90 h-8 rounded-md px-3 text-xs"
+ >
+ Small
+ </button>
+</div>
`;
@@ -1,5 +1,4 @@
import { composeStories } from "@storybook/react-vite";
-import { render } from "@testing-library/react";
import { expect, test } from "vitest";
import * as stories from "./button.stories";
@@ -7,8 +6,8 @@ import * as stories from "./button.stories";
const composed = composeStories(stories);
for (const [name, Story] of Object.entries(composed)) {
- test(`Button/${name} matches snapshot`, () => {
- const { container } = render(<Story />);
- expect(container.firstChild).toMatchSnapshot();
+ test(`Button/${name} matches snapshot`, async () => {
+ await Story.run();
+ expect(document.body.firstChild).toMatchSnapshot();
});
}
@@ -19,6 +19,7 @@ export default mergeConfig(
plugins: [
storybookTest({
configDir: path.join(dirname, ".storybook"),
+ storybookScript: "pnpm storybook --no-open",
}),
],
test: {
@@ -29,6 +30,7 @@ export default mergeConfig(
headless: true,
instances: [{ browser: "chromium" }],
},
+ setupFiles: ["./.storybook/vitest.setup.ts"],
},
},
// Snapshot tests (happy-dom, fast)