claude-toolkit 0.1.9 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +3 -0
- package/bin/cli.ts +0 -0
- package/core/skills/ct-testing-patterns/SKILL.md +117 -0
- package/core/skills/{typescript-conventions → ct-typescript-conventions}/SKILL.md +41 -0
- package/core/skills/{verification-before-completion → ct-verification-before-completion}/SKILL.md +10 -0
- package/docs/README.md +3 -0
- package/docs/best-practices/testing/README.md +84 -0
- package/docs/best-practices/testing/playwright-e2e.md +649 -0
- package/docs/best-practices/testing/storybook-interaction.md +445 -0
- package/docs/best-practices/testing/vitest-unit.md +451 -0
- package/docs/skills/systematic-debugging.md +1 -1
- package/docs/skills/testing-patterns.md +30 -1
- package/docs/skills/typescript-conventions.md +42 -1
- package/docs/skills/verification-before-completion.md +14 -2
- package/docs/stacks/cloudflare-d1-kv.md +35 -1
- package/docs/stacks/i18n-typesafe.md +1 -1
- package/docs/stacks/playwright-patterns.md +179 -0
- package/docs/stacks/protobuf-contracts.md +1 -1
- package/docs/stacks/rust-wasm-patterns.md +2 -2
- package/docs/stacks/solidjs-patterns.md +1 -1
- package/docs/stacks/storybook-patterns.md +160 -0
- package/docs/stacks/vanilla-extract-patterns.md +1 -1
- package/docs/stacks/vite-vitest-patterns.md +485 -0
- package/package.json +3 -3
- package/stacks/cloudflare/skills/{cloudflare-d1-kv → ct-cloudflare-d1-kv}/SKILL.md +22 -0
- package/stacks/playwright/skills/ct-playwright-patterns/SKILL.md +168 -0
- package/stacks/playwright/stack.json +39 -0
- package/stacks/solidjs/stack.json +7 -1
- package/stacks/storybook/skills/ct-storybook-patterns/SKILL.md +149 -0
- package/stacks/storybook/stack.json +46 -0
- package/stacks/vite/skills/ct-vite-vitest-patterns/SKILL.md +492 -0
- package/stacks/vite/stack.json +66 -0
- package/core/skills/testing-patterns/SKILL.md +0 -52
- /package/core/skills/{systematic-debugging → ct-systematic-debugging}/SKILL.md +0 -0
- /package/stacks/i18n-typesafe/skills/{i18n-typesafe → ct-i18n-typesafe}/SKILL.md +0 -0
- /package/stacks/protobuf/skills/{protobuf-contracts → ct-protobuf-contracts}/SKILL.md +0 -0
- /package/stacks/rust-wasm/skills/{rust-wasm-patterns → ct-rust-wasm-patterns}/SKILL.md +0 -0
- /package/stacks/solidjs/skills/{solidjs-patterns → ct-solidjs-patterns}/SKILL.md +0 -0
- /package/stacks/vanilla-extract/skills/{vanilla-extract-patterns → ct-vanilla-extract-patterns}/SKILL.md +0 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
# Storybook Interaction Testing
|
|
2
|
+
|
|
3
|
+
> Sources: [Storybook 10 Blog](https://storybook.js.org/blog/storybook-10/), [Storybook 9.0 Release](https://storybook.js.org/releases/9.0), [Interaction Testing Docs](https://storybook.js.org/docs/writing-tests/interaction-testing), [storybook-solidjs-vite](https://www.npmjs.com/package/storybook-solidjs-vite), [Vitest Addon Docs](https://storybook.js.org/docs/writing-tests/integrations/vitest-addon)
|
|
4
|
+
|
|
5
|
+
Storybook serves as the middle layer of the testing pyramid: component-level interaction testing, visual regression, and accessibility auditing -- all in a real browser environment.
|
|
6
|
+
|
|
7
|
+
## SolidJS Integration
|
|
8
|
+
|
|
9
|
+
The integration is community-maintained via two packages:
|
|
10
|
+
|
|
11
|
+
- **`storybook-solidjs`** -- the renderer
|
|
12
|
+
- **`storybook-solidjs-vite`** -- Vite builder integration (v10.0.x)
|
|
13
|
+
|
|
14
|
+
### Critical: Decorator Reactivity
|
|
15
|
+
|
|
16
|
+
SolidJS components run once; Storybook re-executes decorators on every args/globals change. Standard decorators cause **duplicate DOM elements** with SolidJS.
|
|
17
|
+
|
|
18
|
+
Always use `createJSXDecorator` for JSX-returning decorators:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { createJSXDecorator } from "storybook-solidjs";
|
|
22
|
+
|
|
23
|
+
// CORRECT -- prevents double-rendering
|
|
24
|
+
const ThemeDecorator = createJSXDecorator((Story, context) => (
|
|
25
|
+
<ThemeProvider>
|
|
26
|
+
<Story />
|
|
27
|
+
</ThemeProvider>
|
|
28
|
+
));
|
|
29
|
+
|
|
30
|
+
export default {
|
|
31
|
+
decorators: [ThemeDecorator],
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### CSF Format
|
|
36
|
+
|
|
37
|
+
Use **CSF 3** for SolidJS. CSF Factories (Storybook 10's new format) are React-only as of 10.3.
|
|
38
|
+
|
|
39
|
+
## Writing Stories (CSF 3)
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// Button.stories.tsx
|
|
43
|
+
import type { Meta, StoryObj } from "storybook-solidjs";
|
|
44
|
+
import { Button } from "./Button";
|
|
45
|
+
|
|
46
|
+
const meta = {
|
|
47
|
+
title: "Components/Button",
|
|
48
|
+
component: Button,
|
|
49
|
+
tags: ["autodocs"],
|
|
50
|
+
argTypes: {
|
|
51
|
+
variant: {
|
|
52
|
+
control: "select",
|
|
53
|
+
options: ["primary", "secondary", "ghost"],
|
|
54
|
+
},
|
|
55
|
+
size: {
|
|
56
|
+
control: "radio",
|
|
57
|
+
options: ["sm", "md", "lg"],
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
} satisfies Meta<typeof Button>;
|
|
61
|
+
|
|
62
|
+
export default meta;
|
|
63
|
+
type Story = StoryObj<typeof meta>;
|
|
64
|
+
|
|
65
|
+
export const Primary: Story = {
|
|
66
|
+
args: {
|
|
67
|
+
variant: "primary",
|
|
68
|
+
children: "Click me",
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const Loading: Story = {
|
|
73
|
+
args: {
|
|
74
|
+
variant: "primary",
|
|
75
|
+
loading: true,
|
|
76
|
+
children: "Submitting...",
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const Disabled: Story = {
|
|
81
|
+
args: {
|
|
82
|
+
variant: "primary",
|
|
83
|
+
disabled: true,
|
|
84
|
+
children: "Unavailable",
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Story Coverage Checklist
|
|
90
|
+
|
|
91
|
+
Every component should have stories covering:
|
|
92
|
+
|
|
93
|
+
- All visual states (default, hover, focus, active, disabled, loading, error, empty, success)
|
|
94
|
+
- Viewport sizes (mobile, tablet, desktop) where layout differs
|
|
95
|
+
- Theme variants (light/dark) if applicable
|
|
96
|
+
- Edge cases (long text, missing data, boundary values)
|
|
97
|
+
|
|
98
|
+
## Interaction Testing with Play Functions
|
|
99
|
+
|
|
100
|
+
Play functions turn stories into executable tests. Import everything from `@storybook/test` (instrumented versions of Testing Library + Vitest utilities):
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { expect, fn, userEvent, within, waitFor } from "@storybook/test";
|
|
104
|
+
|
|
105
|
+
export const SubmitForm: Story = {
|
|
106
|
+
args: {
|
|
107
|
+
onSubmit: fn(), // Storybook-instrumented spy
|
|
108
|
+
},
|
|
109
|
+
play: async ({ canvasElement, args, step }) => {
|
|
110
|
+
const canvas = within(canvasElement);
|
|
111
|
+
|
|
112
|
+
await step("Fill in the form", async () => {
|
|
113
|
+
await userEvent.type(canvas.getByLabelText("Email"), "user@example.com");
|
|
114
|
+
await userEvent.type(canvas.getByLabelText("Password"), "secret123");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
await step("Submit the form", async () => {
|
|
118
|
+
await userEvent.click(canvas.getByRole("button", { name: "Sign in" }));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
await step("Verify submission", async () => {
|
|
122
|
+
await expect(args.onSubmit).toHaveBeenCalledOnce();
|
|
123
|
+
await expect(args.onSubmit).toHaveBeenCalledWith({
|
|
124
|
+
email: "user@example.com",
|
|
125
|
+
password: "secret123",
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Key Rules
|
|
133
|
+
|
|
134
|
+
1. **Always `await` expect calls.** This enables proper logging in the Interactions panel.
|
|
135
|
+
2. **Use `step()` to organize** complex interactions into labeled groups.
|
|
136
|
+
3. **Use `fn()` for spying.** Storybook auto-restores mocks between stories.
|
|
137
|
+
4. **Never import from `@testing-library/dom` directly.** Use `@storybook/test` for instrumented versions.
|
|
138
|
+
|
|
139
|
+
## Running Stories as Tests in CI
|
|
140
|
+
|
|
141
|
+
The **Vitest addon** (`@storybook/addon-vitest`) replaces the old `@storybook/test-runner`. It transforms stories into real Vitest tests via a plugin, running in browser mode with Playwright.
|
|
142
|
+
|
|
143
|
+
### Advantages Over the Old Test Runner
|
|
144
|
+
|
|
145
|
+
- Does not need a running Storybook instance
|
|
146
|
+
- Uses Vitest browser mode (real Chromium) for accurate rendering
|
|
147
|
+
- Results appear alongside your Vitest unit tests
|
|
148
|
+
- Faster execution, simpler architecture
|
|
149
|
+
|
|
150
|
+
### Configuration (Vitest 4.x)
|
|
151
|
+
|
|
152
|
+
Use a separate test project:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// vitest.config.storybook.ts
|
|
156
|
+
import { defineConfig } from "vitest/config";
|
|
157
|
+
import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
|
|
158
|
+
|
|
159
|
+
export default defineConfig({
|
|
160
|
+
plugins: [storybookTest()],
|
|
161
|
+
test: {
|
|
162
|
+
name: "storybook",
|
|
163
|
+
browser: {
|
|
164
|
+
enabled: true,
|
|
165
|
+
provider: "playwright",
|
|
166
|
+
instances: [{ browser: "chromium" }],
|
|
167
|
+
},
|
|
168
|
+
setupFiles: [".storybook/vitest.setup.ts"],
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{
|
|
175
|
+
"scripts": {
|
|
176
|
+
"test:storybook": "vitest --project storybook"
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Accessibility Testing
|
|
182
|
+
|
|
183
|
+
Storybook's a11y integration (built on axe-core) catches ~57% of WCAG issues automatically.
|
|
184
|
+
|
|
185
|
+
### Global Configuration
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// .storybook/preview.ts
|
|
189
|
+
export default {
|
|
190
|
+
parameters: {
|
|
191
|
+
a11y: {
|
|
192
|
+
test: "error", // "off" | "todo" | "error"
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
tags: ["a11y-test"],
|
|
196
|
+
};
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
- **`"error"`**: Fail in CI on any violation.
|
|
200
|
+
- **`"todo"`**: Log violations without failing (for gradual adoption).
|
|
201
|
+
- **`"off"`**: Disable for specific stories.
|
|
202
|
+
|
|
203
|
+
### Per-Story Overrides
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
export const CustomA11y: Story = {
|
|
207
|
+
parameters: {
|
|
208
|
+
a11y: {
|
|
209
|
+
config: {
|
|
210
|
+
rules: [
|
|
211
|
+
{ id: "color-contrast", enabled: true },
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Visual Regression Testing
|
|
220
|
+
|
|
221
|
+
### Chromatic (by Storybook Team)
|
|
222
|
+
|
|
223
|
+
- Cloud service that captures screenshots of every story
|
|
224
|
+
- Pixel-perfect diffing across Chrome, Firefox, Safari, Edge
|
|
225
|
+
- Manages baselines across branches and team members
|
|
226
|
+
- Free tier available; paid for scale
|
|
227
|
+
|
|
228
|
+
### Integration
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
bunx chromatic --project-token=<token>
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Add to CI as a quality gate after `test:storybook` passes.
|
|
235
|
+
|
|
236
|
+
## MSW for API-Dependent Components
|
|
237
|
+
|
|
238
|
+
### Global Setup
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
// .storybook/preview.ts
|
|
242
|
+
import { initialize, mswLoader } from "msw-storybook-addon";
|
|
243
|
+
|
|
244
|
+
initialize();
|
|
245
|
+
|
|
246
|
+
export default {
|
|
247
|
+
loaders: [mswLoader],
|
|
248
|
+
};
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Per-Story Handlers
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
import { http, HttpResponse } from "msw";
|
|
255
|
+
|
|
256
|
+
export const WithUserData: Story = {
|
|
257
|
+
parameters: {
|
|
258
|
+
msw: {
|
|
259
|
+
handlers: [
|
|
260
|
+
http.get("/api/user", () =>
|
|
261
|
+
HttpResponse.json({ name: "Alice", email: "alice@example.com" })
|
|
262
|
+
),
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export const WithError: Story = {
|
|
269
|
+
parameters: {
|
|
270
|
+
msw: {
|
|
271
|
+
handlers: [
|
|
272
|
+
http.get("/api/user", () =>
|
|
273
|
+
new HttpResponse(null, { status: 500 })
|
|
274
|
+
),
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Best Practices
|
|
282
|
+
|
|
283
|
+
- Keep "success" handlers in a shared `mocks/handlers.ts` module
|
|
284
|
+
- Override with error/edge-case handlers at the story level
|
|
285
|
+
- Configure MSW to error on unhandled requests
|
|
286
|
+
|
|
287
|
+
### Module Mocking (Storybook 10)
|
|
288
|
+
|
|
289
|
+
Storybook 10 introduced `sb.mock` for module-level mocking (services, utilities). Register in `.storybook/preview.ts` only:
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
// .storybook/preview.ts
|
|
293
|
+
import { sb } from "@storybook/test";
|
|
294
|
+
|
|
295
|
+
sb.mock("../src/api/client", () => ({
|
|
296
|
+
fetchUser: async () => ({ name: "Mock User" }),
|
|
297
|
+
}));
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
MSW remains the right tool for network-level mocking; `sb.mock` is for internal module replacement.
|
|
301
|
+
|
|
302
|
+
## Portable Stories
|
|
303
|
+
|
|
304
|
+
Reuse Storybook stories in your existing Vitest tests with `composeStories`:
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
// Button.test.tsx
|
|
308
|
+
import { composeStories } from "storybook-solidjs";
|
|
309
|
+
import * as stories from "./Button.stories";
|
|
310
|
+
import { render } from "@solidjs/testing-library";
|
|
311
|
+
|
|
312
|
+
const { Primary, Loading, Disabled } = composeStories(stories);
|
|
313
|
+
|
|
314
|
+
test("renders primary button", () => {
|
|
315
|
+
const { getByRole } = render(() => <Primary />);
|
|
316
|
+
expect(getByRole("button")).toBeInTheDocument();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test("can override args in test", () => {
|
|
320
|
+
const { getByRole } = render(() => <Primary children="Override" />);
|
|
321
|
+
expect(getByRole("button")).toHaveTextContent("Override");
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
This preserves all story annotations (args, decorators, loaders, play functions) in unit tests.
|
|
326
|
+
|
|
327
|
+
## vanilla-extract Integration
|
|
328
|
+
|
|
329
|
+
**Zero additional configuration needed.** Storybook uses your `vite.config.ts`, which already includes `@vanilla-extract/vite-plugin`. Styles compile automatically.
|
|
330
|
+
|
|
331
|
+
### Theme Decorator
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
// .storybook/preview.ts
|
|
335
|
+
import { createJSXDecorator } from "storybook-solidjs";
|
|
336
|
+
import "../src/styles/global.css";
|
|
337
|
+
|
|
338
|
+
const withTheme = createJSXDecorator((Story) => (
|
|
339
|
+
<div class={themeClass}>
|
|
340
|
+
<Story />
|
|
341
|
+
</div>
|
|
342
|
+
));
|
|
343
|
+
|
|
344
|
+
export default {
|
|
345
|
+
decorators: [withTheme],
|
|
346
|
+
};
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Organization at Scale
|
|
350
|
+
|
|
351
|
+
### File Structure
|
|
352
|
+
|
|
353
|
+
Organize by feature/domain, not by component type:
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
src/
|
|
357
|
+
features/
|
|
358
|
+
auth/
|
|
359
|
+
LoginForm.tsx
|
|
360
|
+
LoginForm.css.ts
|
|
361
|
+
LoginForm.stories.tsx # Co-located
|
|
362
|
+
LoginForm.test.tsx # Unit tests alongside
|
|
363
|
+
components/
|
|
364
|
+
Button/
|
|
365
|
+
Button.tsx
|
|
366
|
+
Button.css.ts
|
|
367
|
+
Button.stories.tsx
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Tags for Filtering
|
|
371
|
+
|
|
372
|
+
Use tags to control which stories run in different contexts:
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
const meta = {
|
|
376
|
+
tags: ["autodocs", "a11y-test", "interaction-test"],
|
|
377
|
+
// ...
|
|
378
|
+
} satisfies Meta<typeof Component>;
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### Storybook Main Config
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
// .storybook/main.ts
|
|
385
|
+
import type { StorybookConfig } from "storybook-solidjs-vite";
|
|
386
|
+
|
|
387
|
+
const config: StorybookConfig = {
|
|
388
|
+
stories: ["../src/**/*.stories.@(ts|tsx)"],
|
|
389
|
+
framework: "storybook-solidjs-vite",
|
|
390
|
+
addons: [
|
|
391
|
+
"@storybook/addon-essentials",
|
|
392
|
+
"@storybook/addon-a11y",
|
|
393
|
+
"@storybook/addon-vitest",
|
|
394
|
+
],
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
export default config;
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Component Documentation
|
|
401
|
+
|
|
402
|
+
### Autodocs
|
|
403
|
+
|
|
404
|
+
Enable globally or per-component:
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
const meta = {
|
|
408
|
+
tags: ["autodocs"],
|
|
409
|
+
// ...
|
|
410
|
+
} satisfies Meta<typeof Button>;
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Generates prop tables from TypeScript interfaces. Add JSDoc comments on props interfaces for richer docs.
|
|
414
|
+
|
|
415
|
+
### MDX for Custom Docs
|
|
416
|
+
|
|
417
|
+
```mdx
|
|
418
|
+
{/* Button.mdx */}
|
|
419
|
+
import { Meta, Canvas, Controls } from "@storybook/blocks";
|
|
420
|
+
import * as ButtonStories from "./Button.stories";
|
|
421
|
+
|
|
422
|
+
<Meta of={ButtonStories} />
|
|
423
|
+
|
|
424
|
+
# Button
|
|
425
|
+
|
|
426
|
+
Our primary interaction element.
|
|
427
|
+
|
|
428
|
+
<Canvas of={ButtonStories.Primary} />
|
|
429
|
+
<Controls of={ButtonStories.Primary} />
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
## Anti-Patterns
|
|
433
|
+
|
|
434
|
+
| Anti-Pattern | Fix |
|
|
435
|
+
|---|---|
|
|
436
|
+
| Destructuring SolidJS props in stories | Pass props via `args`; use `createJSXDecorator` for decorators |
|
|
437
|
+
| Importing from `@testing-library/dom` directly | Import from `@storybook/test` for instrumented versions |
|
|
438
|
+
| Not awaiting `expect()` in play functions | Always `await expect(...)` |
|
|
439
|
+
| Hardcoding test data in stories | Use `args` and story composition |
|
|
440
|
+
| Giant monolithic story files | One story file per component |
|
|
441
|
+
| Skipping error/edge-case stories | Always include loading, error, empty, boundary states |
|
|
442
|
+
| Using `@storybook/test-runner` with Vite | Use `@storybook/addon-vitest` instead |
|
|
443
|
+
| Not resolving unhandled MSW requests | Configure MSW to error on unhandled requests |
|
|
444
|
+
| Using inline styles in stories | Use theme tokens and `.css.ts` files |
|
|
445
|
+
| Registering `sb.mock` in story files | Register in `.storybook/preview.ts` only |
|