claude-toolkit 0.1.12 → 0.1.20

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.
@@ -0,0 +1,177 @@
1
+ # Storybook Patterns
2
+
3
+ > Storybook interaction testing, CSF 3 stories, play functions, a11y, and visual regression for SolidJS components.
4
+
5
+ **Type:** Stack Skill (requires `storybook` stack)
6
+ **Source:** [`stacks/storybook/skills/ct-storybook-patterns/SKILL.md`](../../stacks/storybook/skills/ct-storybook-patterns/SKILL.md)
7
+ **Directory Mappings:** `.storybook/`, `src/**/*.stories.tsx`
8
+ **File Extensions:** `.stories.tsx`, `.stories.ts`
9
+
10
+ ## Overview
11
+
12
+ Storybook is the middle layer of the testing pyramid: component interaction testing, visual regression, and accessibility auditing in a real browser.
13
+
14
+ ## SolidJS Integration
15
+
16
+ Uses community packages: `storybook-solidjs` (renderer) + `storybook-solidjs-vite` (builder).
17
+
18
+ **Critical:** Always use `createJSXDecorator` for JSX decorators. Standard decorators cause duplicate DOM elements with SolidJS.
19
+
20
+ ```tsx
21
+ import { createJSXDecorator } from "storybook-solidjs";
22
+
23
+ const ThemeDecorator = createJSXDecorator((Story) => (
24
+ <ThemeProvider><Story /></ThemeProvider>
25
+ ));
26
+ ```
27
+
28
+ ## CSF 3 Stories
29
+
30
+ Use CSF 3 (not CSF Factories -- React-only as of Storybook 10.3).
31
+
32
+ ```tsx
33
+ import type { Meta, StoryObj } from "storybook-solidjs";
34
+
35
+ const meta = {
36
+ title: "Components/Button",
37
+ component: Button,
38
+ tags: ["autodocs"],
39
+ } satisfies Meta<typeof Button>;
40
+
41
+ export default meta;
42
+ type Story = StoryObj<typeof meta>;
43
+
44
+ export const Primary: Story = {
45
+ args: { variant: "primary", children: "Click me" },
46
+ };
47
+ ```
48
+
49
+ ### Coverage Checklist
50
+
51
+ Every component needs stories for: all visual states (default, hover, focus, active, disabled, loading, error, empty), viewport sizes where layout differs, theme variants, edge cases (long text, missing data).
52
+
53
+ ## Interaction Testing
54
+
55
+ Import everything from `@storybook/test` (instrumented versions). Never import from `@testing-library/dom` directly.
56
+
57
+ ```tsx
58
+ import { expect, fn, userEvent, within } from "@storybook/test";
59
+
60
+ export const SubmitForm: Story = {
61
+ args: { onSubmit: fn() },
62
+ play: async ({ canvasElement, args, step }) => {
63
+ const canvas = within(canvasElement);
64
+ await step("Fill form", async () => {
65
+ await userEvent.type(canvas.getByLabelText("Email"), "user@example.com");
66
+ });
67
+ await step("Submit", async () => {
68
+ await userEvent.click(canvas.getByRole("button", { name: "Sign in" }));
69
+ });
70
+ await expect(args.onSubmit).toHaveBeenCalledOnce();
71
+ },
72
+ };
73
+ ```
74
+
75
+ **Key rules:**
76
+ 1. Always `await` expect calls (enables Interactions panel logging).
77
+ 2. Use `step()` to organize complex interactions.
78
+ 3. Use `fn()` for spying (auto-restored between stories).
79
+
80
+ ## Running in CI (Vitest Addon)
81
+
82
+ Use `@storybook/addon-vitest` (replaces old test-runner). No running Storybook instance needed.
83
+
84
+ Required dependencies: `@storybook/addon-vitest`, `@vitest/browser`, `@vitest/browser-playwright`, `@vitest/coverage-v8`.
85
+
86
+ Add as an inline project in your main Vite config using `test.projects`:
87
+
88
+ ```typescript
89
+ // vite.config.ts
90
+ import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
91
+ import { playwright } from "@vitest/browser-playwright";
92
+
93
+ export default defineConfig({
94
+ plugins: [/* ...app plugins */],
95
+ test: {
96
+ projects: [
97
+ {
98
+ extends: true,
99
+ test: { name: "unit", environment: "jsdom", setupFiles: ["./tests/setup.ts"] },
100
+ },
101
+ {
102
+ extends: true,
103
+ plugins: [storybookTest({ configDir: ".storybook" })],
104
+ test: {
105
+ name: "storybook",
106
+ browser: { enabled: true, headless: true, provider: playwright(), instances: [{ browser: "chromium" }] },
107
+ setupFiles: [".storybook/vitest.setup.ts"],
108
+ },
109
+ },
110
+ ],
111
+ },
112
+ });
113
+ ```
114
+
115
+ ## Accessibility
116
+
117
+ Built on axe-core. Set globally in `.storybook/preview.ts`:
118
+
119
+ ```typescript
120
+ export default {
121
+ parameters: { a11y: { test: "error" } }, // "error" | "todo" | "off"
122
+ tags: ["a11y-test"],
123
+ };
124
+ ```
125
+
126
+ ## MSW for API Components
127
+
128
+ ```typescript
129
+ // .storybook/preview.ts
130
+ import { initialize, mswLoader } from "msw-storybook-addon";
131
+ initialize();
132
+ export default { loaders: [mswLoader] };
133
+ ```
134
+
135
+ Per-story handlers via `parameters.msw.handlers`. Keep success handlers in shared `mocks/handlers.ts`, override with error/edge-case at story level.
136
+
137
+ ## Module Mocking (Storybook 10)
138
+
139
+ `sb.mock` for internal module replacement. Register in `.storybook/preview.ts` only:
140
+
141
+ ```typescript
142
+ import { sb } from "@storybook/test";
143
+ sb.mock("../src/api/client", () => ({ fetchUser: async () => ({ name: "Mock" }) }));
144
+ ```
145
+
146
+ MSW for network-level; `sb.mock` for internal modules.
147
+
148
+ ## Portable Stories
149
+
150
+ Reuse stories in Vitest with `composeStories`:
151
+
152
+ ```tsx
153
+ import { composeStories } from "storybook-solidjs";
154
+ import * as stories from "./Button.stories";
155
+ const { Primary } = composeStories(stories);
156
+
157
+ test("renders primary", () => {
158
+ const { getByRole } = render(() => <Primary />);
159
+ expect(getByRole("button")).toBeInTheDocument();
160
+ });
161
+ ```
162
+
163
+ ## Anti-Patterns
164
+
165
+ 1. **Destructuring SolidJS props in stories** -- Pass props via `args`; use `createJSXDecorator` for decorators.
166
+ 2. **Importing from `@testing-library/dom`** -- Use `@storybook/test` for instrumented versions.
167
+ 3. **Not awaiting `expect()` in play functions** -- Always `await expect(...)`.
168
+ 4. **Skipping error/edge-case stories** -- Always include loading, error, empty, boundary states.
169
+ 5. **Using `@storybook/test-runner`** -- Use `@storybook/addon-vitest` instead.
170
+ 6. **Registering `sb.mock` in story files** -- Register in `.storybook/preview.ts` only.
171
+
172
+ ## See Also
173
+
174
+ - [ct-testing-patterns](../skills/testing-patterns.md) -- Framework-agnostic TDD practices
175
+ - [Storybook Interaction Best Practices](../best-practices/testing/storybook-interaction.md) -- SolidJS-specific Storybook patterns
176
+ - [ct-vite-vitest-patterns](vite-vitest-patterns.md) -- Unit testing layer
177
+ - [ct-playwright-patterns](playwright-patterns.md) -- E2E testing layer
@@ -0,0 +1,485 @@
1
+ # Vite + Vitest Patterns
2
+
3
+ > Vite build tooling and Vitest unit/integration testing patterns.
4
+
5
+ **Type:** Stack Skill (requires `vite` stack)
6
+ **Source:** [`stacks/vite/skills/ct-vite-vitest-patterns/SKILL.md`](../../stacks/vite/skills/ct-vite-vitest-patterns/SKILL.md)
7
+ **Directory Mappings:** `vite.config.ts`, `vitest.config.ts`, `src/**/*.test.ts`, `tests/`
8
+ **File Extensions:** `.test.ts`, `.test.tsx`, `.spec.ts`, `.spec.tsx`
9
+
10
+ ## Overview
11
+
12
+ Vite is the build tool; Vitest is its native test runner. They share a unified config -- aliases, plugins, and `define` values work identically in both. This is the primary advantage over separate build/test toolchains.
13
+
14
+ ## Vite Configuration
15
+
16
+ ### Config Structure
17
+
18
+ ```typescript
19
+ import { defineConfig } from 'vite'
20
+
21
+ export default defineConfig({
22
+ base: '/',
23
+ plugins: [],
24
+ envPrefix: 'VITE_',
25
+
26
+ resolve: {
27
+ alias: { '@': '/src' },
28
+ conditions: [],
29
+ },
30
+
31
+ css: {
32
+ transformer: 'postcss', // or 'lightningcss'
33
+ modules: {},
34
+ preprocessorOptions: {},
35
+ },
36
+
37
+ oxc: {}, // Oxc transformer (replaces esbuild in Vite 8)
38
+
39
+ server: {
40
+ port: 5173,
41
+ proxy: {},
42
+ warmup: { clientFiles: [] }, // pre-transform hot files for faster cold starts
43
+ },
44
+
45
+ build: {
46
+ target: 'baseline-widely-available',
47
+ outDir: 'dist',
48
+ sourcemap: false,
49
+ minify: 'oxc', // 'oxc' (default) | 'terser' | false
50
+ rolldownOptions: {}, // Rolldown bundler config (replaces rollupOptions in Vite 8)
51
+ },
52
+
53
+ ssr: {
54
+ target: 'node',
55
+ noExternal: [],
56
+ external: [],
57
+ },
58
+ })
59
+ ```
60
+
61
+ ### Conditional Config
62
+
63
+ ```typescript
64
+ export default defineConfig(({ command, mode, isSsrBuild }) => {
65
+ if (command === 'serve') return { /* dev-specific */ }
66
+ return { /* build-specific */ }
67
+ })
68
+ ```
69
+
70
+ ### Environment Variables
71
+
72
+ Loading order (later overrides earlier): `.env` > `.env.local` > `.env.[mode]` > `.env.[mode].local`.
73
+
74
+ Built-in `import.meta.env`: `MODE`, `BASE_URL`, `PROD`, `DEV`, `SSR`.
75
+
76
+ **Security:** Never put secrets in `VITE_*` variables -- they are embedded in client bundles.
77
+
78
+ TypeScript declarations (`src/vite-env.d.ts`):
79
+
80
+ ```typescript
81
+ /// <reference types="vite/client" />
82
+
83
+ interface ImportMetaEnv {
84
+ readonly VITE_API_URL: string
85
+ }
86
+ interface ImportMeta {
87
+ readonly env: ImportMetaEnv
88
+ }
89
+ ```
90
+
91
+ ### Plugin System
92
+
93
+ ```typescript
94
+ export default defineConfig({
95
+ plugins: [
96
+ solidPlugin(),
97
+ process.env.ANALYZE && visualizer(), // conditional (falsy ignored)
98
+ { ...myPlugin(), enforce: 'pre' }, // before Vite core transforms
99
+ { ...myPlugin(), apply: 'build' }, // build only
100
+ ],
101
+ })
102
+ ```
103
+
104
+ Key hooks (execution order): `config` > `configResolved` > `configureServer` > `transformIndexHtml` > `resolveId` > `load` > `transform` > `handleHotUpdate`.
105
+
106
+ ### Library Mode
107
+
108
+ ```typescript
109
+ import { resolve } from 'node:path'
110
+
111
+ export default defineConfig({
112
+ build: {
113
+ lib: {
114
+ entry: resolve(import.meta.dirname, 'lib/main.ts'),
115
+ name: 'MyLib',
116
+ fileName: 'my-lib',
117
+ formats: ['es', 'cjs'],
118
+ },
119
+ rolldownOptions: {
120
+ external: ['solid-js', 'solid-js/web'], // always externalize peer deps
121
+ },
122
+ },
123
+ })
124
+ ```
125
+
126
+ ### SSR
127
+
128
+ ```json
129
+ {
130
+ "build:client": "vite build --outDir dist/client",
131
+ "build:server": "vite build --outDir dist/server --ssr src/entry-server.ts"
132
+ }
133
+ ```
134
+
135
+ Use `middlewareMode: true` + `appType: 'custom'` for framework SSR integration.
136
+
137
+ ### Build Performance
138
+
139
+ 1. **Avoid barrel files** -- import directly from source modules, not re-export `index.ts`
140
+ 2. **Use explicit extensions** -- `import './Component.tsx'` not `import './Component'`
141
+ 3. **Warm up hot files** -- `server.warmup.clientFiles: ['./src/main.tsx']`
142
+ 4. **Prefer native CSS** -- CSS nesting is supported natively; avoid Sass when possible
143
+ 5. **Narrow resolve.extensions** -- remove extensions you don't use
144
+
145
+ ## Vitest Configuration
146
+
147
+ ### Standalone Config
148
+
149
+ ```typescript
150
+ import { defineConfig } from 'vitest/config'
151
+
152
+ export default defineConfig({
153
+ test: {
154
+ include: ['**/*.{test,spec}.{ts,tsx}'],
155
+ environment: 'node', // 'node' | 'jsdom' | 'happy-dom'
156
+ globals: false,
157
+ setupFiles: [],
158
+ testTimeout: 5000,
159
+ pool: 'forks', // 'forks' | 'threads' | 'vmThreads'
160
+
161
+ restoreMocks: true, // auto vi.restoreAllMocks() after each test
162
+ clearMocks: true,
163
+
164
+ coverage: {
165
+ provider: 'v8',
166
+ include: ['src/**/*.{ts,tsx}'],
167
+ exclude: ['**/*.test.*', '**/*.d.ts', 'src/types/**'],
168
+ reporter: ['text', 'html', 'lcov'],
169
+ thresholds: { lines: 80, branches: 80, functions: 80, statements: 80 },
170
+ },
171
+
172
+ snapshotSerializers: [],
173
+ },
174
+ })
175
+ ```
176
+
177
+ ### Integrated with Vite Config
178
+
179
+ ```typescript
180
+ /// <reference types="vitest/config" />
181
+ import { defineConfig } from 'vite'
182
+
183
+ export default defineConfig({
184
+ plugins: [solidPlugin()],
185
+ resolve: { alias: { '@': '/src' } }, // shared: works in both app and tests
186
+ test: {
187
+ environment: 'jsdom',
188
+ setupFiles: ['./tests/setup.ts'],
189
+ },
190
+ })
191
+ ```
192
+
193
+ Vite's `resolve.alias`, `plugins`, and `define` are automatically inherited by Vitest. Never duplicate them.
194
+
195
+ ### Merging Separate Configs
196
+
197
+ ```typescript
198
+ import { defineConfig, mergeConfig } from 'vitest/config'
199
+ import viteConfig from './vite.config'
200
+
201
+ export default mergeConfig(viteConfig, defineConfig({
202
+ test: { environment: 'jsdom' },
203
+ }))
204
+ ```
205
+
206
+ ### Workspace / Projects (Vitest 3+)
207
+
208
+ Use `test.projects` for monorepos or mixed environments:
209
+
210
+ ```typescript
211
+ export default defineConfig({
212
+ test: {
213
+ projects: [
214
+ 'packages/*',
215
+ {
216
+ test: {
217
+ name: 'unit',
218
+ include: ['src/**/*.test.ts'],
219
+ environment: 'node',
220
+ },
221
+ },
222
+ {
223
+ test: {
224
+ name: 'components',
225
+ include: ['src/**/*.test.tsx'],
226
+ environment: 'jsdom',
227
+ },
228
+ },
229
+ ],
230
+ },
231
+ })
232
+ ```
233
+
234
+ ## Mocking
235
+
236
+ ### Mock Functions
237
+
238
+ ```typescript
239
+ const fn = vi.fn()
240
+ fn.mockReturnValue(42)
241
+ fn.mockResolvedValue({ data: [] })
242
+ fn.mockImplementation((x) => x * 2)
243
+
244
+ expect(fn).toHaveBeenCalledWith('arg')
245
+ expect(fn).toHaveBeenCalledTimes(1)
246
+ ```
247
+
248
+ ### Module Mocking
249
+
250
+ `vi.mock` is hoisted above imports automatically:
251
+
252
+ ```typescript
253
+ vi.mock('./api', () => ({
254
+ fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'Test' }),
255
+ }))
256
+
257
+ // Access original implementation inside mock
258
+ vi.mock('./utils', async (importOriginal) => {
259
+ const actual = await importOriginal<typeof import('./utils')>()
260
+ return { ...actual, format: vi.fn() }
261
+ })
262
+ ```
263
+
264
+ **Hoisting caveat:** Variables defined before `vi.mock()` are not accessible inside the factory. Use `vi.hoisted()` for shared variables:
265
+
266
+ ```typescript
267
+ const { mockFetch } = vi.hoisted(() => ({
268
+ mockFetch: vi.fn(),
269
+ }))
270
+
271
+ vi.mock('./api', () => ({ fetch: mockFetch }))
272
+ ```
273
+
274
+ ### Spying
275
+
276
+ ```typescript
277
+ const spy = vi.spyOn(console, 'warn').mockImplementation(() => {})
278
+ expect(spy).toHaveBeenCalledWith('expected warning')
279
+ spy.mockRestore()
280
+ ```
281
+
282
+ ### Timer Mocking
283
+
284
+ ```typescript
285
+ vi.useFakeTimers()
286
+ vi.setSystemTime(new Date('2026-01-01'))
287
+
288
+ setTimeout(callback, 1000)
289
+ vi.advanceTimersByTime(1000)
290
+ expect(callback).toHaveBeenCalled()
291
+
292
+ vi.useRealTimers()
293
+ ```
294
+
295
+ ### Environment Stubs
296
+
297
+ ```typescript
298
+ vi.stubGlobal('__VERSION__', '1.0.0')
299
+ vi.stubEnv('VITE_API_URL', 'http://test.example.com')
300
+
301
+ afterEach(() => {
302
+ vi.unstubAllGlobals()
303
+ vi.unstubAllEnvs()
304
+ })
305
+ ```
306
+
307
+ ## Snapshot Testing
308
+
309
+ ```typescript
310
+ // File snapshots (stored in __snapshots__/)
311
+ expect(result).toMatchSnapshot()
312
+
313
+ // Inline snapshots (written into test file by vitest --update)
314
+ expect(result).toMatchInlineSnapshot(`{ "id": 1 }`)
315
+
316
+ // Custom file path
317
+ expect(htmlOutput).toMatchFileSnapshot('./fixtures/expected.html')
318
+ ```
319
+
320
+ Keep inline snapshots under 10-15 lines. Use file snapshots for complex output. Snapshots catch regressions but don't verify correctness -- combine with explicit assertions on critical values.
321
+
322
+ ## In-Source Testing
323
+
324
+ ```typescript
325
+ // src/utils/math.ts
326
+ export function add(...args: number[]) {
327
+ return args.reduce((a, b) => a + b, 0)
328
+ }
329
+
330
+ if (import.meta.vitest) {
331
+ const { it, expect } = import.meta.vitest
332
+ it('adds numbers', () => {
333
+ expect(add(1, 2, 3)).toBe(6)
334
+ })
335
+ }
336
+ ```
337
+
338
+ Config:
339
+
340
+ ```typescript
341
+ export default defineConfig({
342
+ test: { includeSource: ['src/**/*.ts'] },
343
+ define: { 'import.meta.vitest': 'undefined' }, // tree-shake from production
344
+ })
345
+ ```
346
+
347
+ Best for small utility functions. Use separate test files for components and integration tests.
348
+
349
+ ## Browser Mode (Vitest 4+, Stable)
350
+
351
+ ```typescript
352
+ import { playwright } from '@vitest/browser-playwright'
353
+
354
+ export default defineConfig({
355
+ test: {
356
+ browser: {
357
+ enabled: true,
358
+ provider: playwright(),
359
+ instances: [{ browser: 'chromium' }],
360
+ headless: true,
361
+ },
362
+ },
363
+ })
364
+ ```
365
+
366
+ Providers: `@vitest/browser-playwright` (recommended), `@vitest/browser-webdriverio`.
367
+
368
+ Quick setup: `npx vitest init browser`
369
+
370
+ ### Visual Regression
371
+
372
+ ```typescript
373
+ import { page } from 'vitest/browser'
374
+
375
+ it('matches visual baseline', async () => {
376
+ await page.goto('/component-demo')
377
+ await expect(page.getByRole('button')).toMatchScreenshot()
378
+ })
379
+ ```
380
+
381
+ ### Playwright Traces
382
+
383
+ ```typescript
384
+ browser: {
385
+ provider: playwright({ trace: 'on-first-retry' }),
386
+ // 'off' | 'on' | 'on-first-retry' | 'on-all-retries' | 'retain-on-failure'
387
+ }
388
+ ```
389
+
390
+ ### Browser Mode Limitations
391
+
392
+ - Cannot `vi.spyOn` module exports -- use `vi.mock('./module', { spy: true })` instead
393
+ - Thread-blocking APIs (`alert`, `confirm`, `prompt`) are auto-mocked
394
+ - Uses testing-library selectors: `getByRole`, `getByText`, `getByLabelText`
395
+
396
+ ## Coverage
397
+
398
+ Install: `@vitest/coverage-v8` (default, fastest) or `@vitest/coverage-istanbul` (universal).
399
+
400
+ Run: `vitest --coverage`
401
+
402
+ Ignore comments:
403
+
404
+ - V8: `/* v8 ignore next */`
405
+ - Istanbul: `/* istanbul ignore next -- @preserve */`
406
+
407
+ Include `@preserve` to prevent Oxc minifier from stripping comments.
408
+
409
+ ## Custom Matchers
410
+
411
+ ```typescript
412
+ import { expect } from 'vitest'
413
+
414
+ expect.extend({
415
+ toBeWithinRange(received: number, floor: number, ceiling: number) {
416
+ const pass = received >= floor && received <= ceiling
417
+ return {
418
+ pass,
419
+ message: () => `expected ${received} to be within [${floor}, ${ceiling}]`,
420
+ }
421
+ },
422
+ })
423
+ ```
424
+
425
+ TypeScript (`vitest.d.ts`):
426
+
427
+ ```typescript
428
+ import 'vitest'
429
+
430
+ declare module 'vitest' {
431
+ interface Matchers<T = any> {
432
+ toBeWithinRange(floor: number, ceiling: number): T
433
+ }
434
+ }
435
+ ```
436
+
437
+ ### Schema Validation (Vitest 4+)
438
+
439
+ ```typescript
440
+ import { z } from 'zod'
441
+
442
+ const UserSchema = z.object({ id: z.number(), name: z.string() })
443
+ expect(response.data).toSatisfy(expect.schemaMatching(UserSchema))
444
+ ```
445
+
446
+ Works with any Standard Schema v1 library (Zod, Valibot, ArkType).
447
+
448
+ ## Vite 8 Migration
449
+
450
+ | Old (Vite 7-) | New (Vite 8) | Status |
451
+ | ---------------------------- | ---------------------------- | ------------------------------- |
452
+ | `build.rollupOptions` | `build.rolldownOptions` | Deprecated, compat layer exists |
453
+ | `optimizeDeps.esbuildOptions` | `optimizeDeps.rolldownOptions` | Deprecated |
454
+ | `esbuild` config | `oxc` config | Deprecated |
455
+ | `build.minify: 'esbuild'` | `build.minify: 'oxc'` | New default |
456
+
457
+ The compat layer auto-converts old keys. New code should use the new keys.
458
+
459
+ ## Anti-Patterns
460
+
461
+ ### Vite
462
+
463
+ 1. **Barrel file sprawl** -- Re-exporting through `index.ts` forces transforming all modules. Import directly from source.
464
+ 2. **Secrets in VITE_ variables** -- Embedded in client bundles. Use server-only env vars, proxy through API routes.
465
+ 3. **Duplicating resolve config for tests** -- Vitest inherits Vite's aliases and plugins. Don't redeclare.
466
+ 4. **Using `rollupOptions` in Vite 8** -- Works via compat but generates warnings. Use `rolldownOptions`.
467
+ 5. **Not externalizing peer deps in library mode** -- Bundling framework deps causes duplicate instances.
468
+
469
+ ### Vitest
470
+
471
+ 1. **Over-mocking internal modules** -- Mock at boundaries (HTTP, filesystem, external APIs), not internal functions.
472
+ 2. **Forgetting vi.mock is hoisted** -- Variables before `vi.mock()` aren't accessible in the factory. Use `vi.hoisted()`.
473
+ 3. **Not restoring mocks** -- Set `restoreMocks: true` in config. Leaked mocks cause cascading failures.
474
+ 4. **Using jsdom when node suffices** -- jsdom adds overhead. Use workspace projects to split: `node` for logic, `jsdom` for components.
475
+ 5. **Large inline snapshots** -- Over 10-15 lines reduces readability. Use `toMatchFileSnapshot`.
476
+ 6. **Snapshot-only testing** -- Snapshots catch regressions but don't verify correctness. Add explicit assertions.
477
+ 7. **Global jsdom environment** -- Use `test.projects` to run DOM tests in jsdom and logic tests in node.
478
+ 8. **Using `vi.spyOn` in browser mode** -- Use `vi.mock('./module', { spy: true })` instead.
479
+
480
+ ## See Also
481
+
482
+ - [ct-testing-patterns](../skills/testing-patterns.md) -- Framework-agnostic TDD practices
483
+ - [Vitest Unit Testing Best Practices](../best-practices/testing/vitest-unit.md) -- SolidJS-specific Vitest patterns
484
+ - [ct-storybook-patterns](storybook-patterns.md) -- Interaction testing layer
485
+ - [ct-playwright-patterns](playwright-patterns.md) -- E2E testing layer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-toolkit",
3
- "version": "0.1.12",
3
+ "version": "0.1.20",
4
4
  "description": "Reusable Claude Code configuration toolkit with stack-specific connectors",
5
5
  "type": "module",
6
6
  "bin": {