claude-toolkit 0.1.12 → 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.
@@ -0,0 +1,451 @@
1
+ # Vitest Unit Testing
2
+
3
+ > Sources: [Vitest 4.0 Announcement](https://voidzero.dev/posts/announcing-vitest-4), [Vitest 4.1 Blog](https://main.vitest.dev/blog/vitest-4-1), [Vitest Docs](https://vitest.dev/), [SolidJS Testing Docs](https://docs.solidjs.com/guides/testing), [@solidjs/testing-library](https://github.com/solidjs/solid-testing-library)
4
+
5
+ Unit tests form the base of the testing pyramid. They are fast, isolated, and cover pure logic, reactive primitives, and component behavior in a JSDOM environment.
6
+
7
+ ## AAA Pattern
8
+
9
+ Every test follows **Arrange-Act-Assert**. Keep each phase visually distinct:
10
+
11
+ ```typescript
12
+ it("should format currency correctly", () => {
13
+ // Arrange
14
+ const amount = 1234.5;
15
+
16
+ // Act
17
+ const result = formatCurrency(amount, "NZD");
18
+
19
+ // Assert
20
+ expect(result).toBe("$1,234.50");
21
+ });
22
+ ```
23
+
24
+ For simple tests, the phases can be implicit. For complex tests, add blank lines between them.
25
+
26
+ ## Naming Conventions
27
+
28
+ - **Files**: `*.test.ts` / `*.test.tsx`, co-located with source or in `tests/`
29
+ - **Describe blocks**: Name the unit under test (`describe("formatCurrency", ...)`)
30
+ - **It blocks**: Describe behavior, not implementation ("should return loading state when resource is pending", not "should set isLoading to true")
31
+
32
+ ## Testing SolidJS Components
33
+
34
+ ### render() Takes a Callback
35
+
36
+ This is different from React Testing Library. SolidJS needs the callback form to establish a reactive root:
37
+
38
+ ```tsx
39
+ import { render, screen } from "@solidjs/testing-library";
40
+ import { Button } from "../src/components/Button";
41
+
42
+ it("should render button text", () => {
43
+ render(() => <Button label="Click me" />);
44
+ expect(screen.getByRole("button")).toHaveTextContent("Click me");
45
+ });
46
+ ```
47
+
48
+ ### Testing Signals with renderHook
49
+
50
+ For hooks/composables that return reactive state:
51
+
52
+ ```typescript
53
+ import { renderHook } from "@solidjs/testing-library";
54
+ import { useCounter } from "../src/hooks/useCounter";
55
+
56
+ it("should increment counter", () => {
57
+ const { result } = renderHook(useCounter);
58
+ expect(result.count).toBe(0);
59
+ result.increment();
60
+ expect(result.count).toBe(1);
61
+ });
62
+ ```
63
+
64
+ ### Testing Signals with createRoot
65
+
66
+ For primitives that don't need component context, `createRoot` is sufficient and faster:
67
+
68
+ ```typescript
69
+ import { createRoot, createSignal, createMemo } from "solid-js";
70
+
71
+ it("should compute derived value", () => {
72
+ createRoot((dispose) => {
73
+ const [count, setCount] = createSignal(0);
74
+ const doubled = createMemo(() => count() * 2);
75
+
76
+ expect(doubled()).toBe(0);
77
+ setCount(5);
78
+ expect(doubled()).toBe(10);
79
+
80
+ dispose();
81
+ });
82
+ });
83
+ ```
84
+
85
+ ### Testing Effects with testEffect
86
+
87
+ Effects are async in SolidJS. The `testEffect` utility provides a `done` callback for async assertion:
88
+
89
+ ```typescript
90
+ import { testEffect } from "@solidjs/testing-library";
91
+ import { createSignal, createEffect } from "solid-js";
92
+
93
+ it("should react to signal changes", () => {
94
+ const [value, setValue] = createSignal(0);
95
+
96
+ return testEffect((done) =>
97
+ createEffect((run: number = 0) => {
98
+ if (run === 0) {
99
+ expect(value()).toBe(0);
100
+ setValue(1);
101
+ } else if (run === 1) {
102
+ expect(value()).toBe(1);
103
+ done();
104
+ }
105
+ return run + 1;
106
+ })
107
+ );
108
+ });
109
+ ```
110
+
111
+ ### Testing Resources (createResource)
112
+
113
+ Resources trigger Suspense. Wrap in `<Suspense>` and assert loading/data/error states:
114
+
115
+ ```tsx
116
+ import { render, screen } from "@solidjs/testing-library";
117
+ import { Suspense } from "solid-js";
118
+
119
+ it("should show loading then data", async () => {
120
+ render(() => (
121
+ <Suspense fallback={<div>Loading...</div>}>
122
+ <MyResourceComponent />
123
+ </Suspense>
124
+ ));
125
+
126
+ expect(screen.getByText("Loading...")).toBeInTheDocument();
127
+ expect(await screen.findByText("Data loaded")).toBeInTheDocument();
128
+ });
129
+ ```
130
+
131
+ ## Mocking Strategies
132
+
133
+ ### Decision Framework
134
+
135
+ | Scenario | Tool |
136
+ |---|---|
137
+ | Replace entire module | `vi.mock("module")` |
138
+ | Spy on a specific method | `vi.spyOn(obj, "method")` |
139
+ | Network requests | MSW (`msw/node`) |
140
+ | Vary mock per test | `vi.hoisted` + `mockReturnValue` |
141
+ | Non-hoisted dynamic mock | `vi.doMock` |
142
+
143
+ ### vi.mock with vi.hoisted (Recommended Pattern)
144
+
145
+ `vi.hoisted` lets you declare mock references that are accessible inside `vi.mock` (which is hoisted to the top of the file):
146
+
147
+ ```typescript
148
+ const { mockFetch } = vi.hoisted(() => ({
149
+ mockFetch: vi.fn(),
150
+ }));
151
+
152
+ vi.mock("../src/api/client", () => ({
153
+ fetchData: mockFetch,
154
+ }));
155
+
156
+ it("should handle API error", () => {
157
+ mockFetch.mockRejectedValue(new Error("Network error"));
158
+ // ... test
159
+ });
160
+ ```
161
+
162
+ ### vi.spyOn for Partial Mocking
163
+
164
+ When you only need to intercept one method while keeping the rest real:
165
+
166
+ ```typescript
167
+ import * as utils from "../src/utils";
168
+
169
+ it("should call logger", () => {
170
+ const spy = vi.spyOn(utils, "log");
171
+ doSomething();
172
+ expect(spy).toHaveBeenCalledWith("action completed");
173
+ spy.mockRestore();
174
+ });
175
+ ```
176
+
177
+ ### MSW for Network Mocking
178
+
179
+ Mock Service Worker intercepts at the network level, giving realistic integration tests:
180
+
181
+ ```typescript
182
+ import { setupServer } from "msw/node";
183
+ import { http, HttpResponse } from "msw";
184
+
185
+ const server = setupServer(
186
+ http.get("/api/user", () => HttpResponse.json({ name: "Alice" }))
187
+ );
188
+
189
+ beforeAll(() => server.listen());
190
+ afterEach(() => server.resetHandlers());
191
+ afterAll(() => server.close());
192
+
193
+ it("should display user name", async () => {
194
+ render(() => <UserProfile />);
195
+ expect(await screen.findByText("Alice")).toBeInTheDocument();
196
+ });
197
+ ```
198
+
199
+ ### mockThrow / mockThrowOnce (Vitest 4.1+)
200
+
201
+ Concise error path testing without wrapping in functions:
202
+
203
+ ```typescript
204
+ mockFn.mockThrow(new Error("failed"));
205
+ mockFn.mockThrowOnce(new Error("first call fails"));
206
+ ```
207
+
208
+ ### Type-Safe Mocks
209
+
210
+ Use `vi.mocked()` for typed access to mocked functions:
211
+
212
+ ```typescript
213
+ import { fetchUser } from "../src/api";
214
+ vi.mock("../src/api");
215
+
216
+ const mockedFetchUser = vi.mocked(fetchUser);
217
+ mockedFetchUser.mockResolvedValue({ name: "Alice" });
218
+ ```
219
+
220
+ ## Snapshot Testing
221
+
222
+ ### When to Use
223
+
224
+ - Serialized objects, API response shapes, configuration objects
225
+ - Small component output (buttons, cards, form fields)
226
+
227
+ ### When NOT to Use
228
+
229
+ - Large pages or complex component trees
230
+ - Frequently changing UI
231
+ - Dynamic content (timestamps, IDs, random values)
232
+
233
+ ### Prefer Inline Snapshots
234
+
235
+ Keeps expected output visible in the test file:
236
+
237
+ ```typescript
238
+ expect(formatUser(user)).toMatchInlineSnapshot(`
239
+ {
240
+ "name": "Alice",
241
+ "role": "admin",
242
+ }
243
+ `);
244
+ ```
245
+
246
+ ### Handle Dynamic Values
247
+
248
+ ```typescript
249
+ expect(result).toMatchObject({
250
+ id: expect.any(String),
251
+ createdAt: expect.any(Date),
252
+ name: "Alice",
253
+ });
254
+ ```
255
+
256
+ ### Snapshot Hygiene
257
+
258
+ - Review snapshot diffs as carefully as code diffs in PRs
259
+ - Run `vitest --update` only intentionally, never blindly
260
+ - CI should fail on obsolete snapshots
261
+
262
+ ## Type Testing
263
+
264
+ Vitest has built-in type testing (since 2.1). Verify your types work correctly alongside runtime:
265
+
266
+ ```typescript
267
+ import { expectTypeOf } from "vitest";
268
+
269
+ it("should return correct type", () => {
270
+ const result = parseConfig(rawConfig);
271
+ expectTypeOf(result).toEqualTypeOf<AppConfig>();
272
+ expectTypeOf(result.port).toBeNumber();
273
+ });
274
+ ```
275
+
276
+ ## Coverage
277
+
278
+ ### Use V8 Provider
279
+
280
+ Since Vitest 3.2, V8 coverage uses AST-based remapping that matches Istanbul accuracy with ~10% overhead vs Istanbul's ~300%. Always use V8:
281
+
282
+ ```typescript
283
+ // vite.config.ts
284
+ test: {
285
+ coverage: {
286
+ provider: "v8",
287
+ include: ["src/**/*.{ts,tsx}"],
288
+ exclude: [
289
+ "src/**/*.css.ts", // vanilla-extract style files
290
+ "src/**/index.ts", // barrel exports
291
+ "src/**/*.d.ts", // type declarations
292
+ "src/locales/**", // i18n generated files
293
+ ],
294
+ reporter: ["text", "html", "lcov"],
295
+ thresholds: {
296
+ statements: 80,
297
+ branches: 70,
298
+ functions: 80,
299
+ lines: 80,
300
+ },
301
+ },
302
+ }
303
+ ```
304
+
305
+ ### Coverage Recommendations
306
+
307
+ - Start with moderate thresholds (70-80%) and ratchet up as coverage grows
308
+ - Focus on **branch coverage** -- it catches more real bugs than line coverage
309
+ - Use per-glob overrides for critical paths (e.g., `src/utils/**` at 95%)
310
+ - Use `/* v8 ignore start */` / `/* v8 ignore stop */` for intentionally uncovered code
311
+
312
+ ## Vitest 4.x Features
313
+
314
+ ### Test Tags
315
+
316
+ Define tags with per-tag configuration. Useful for separating fast/slow tests:
317
+
318
+ ```typescript
319
+ // vite.config.ts
320
+ test: {
321
+ tags: {
322
+ slow: { timeout: 30_000 },
323
+ flaky: { retry: 3 },
324
+ },
325
+ }
326
+ ```
327
+
328
+ ```typescript
329
+ it.tags(["slow"])("should process large dataset", async () => {
330
+ // ...
331
+ });
332
+ ```
333
+
334
+ ### Builder-Pattern Fixtures
335
+
336
+ The idiomatic way to share setup in Vitest 4.x. TypeScript infers fixture types automatically:
337
+
338
+ ```typescript
339
+ const myTest = test
340
+ .extend({ config: { timeout: 5000 } })
341
+ .extend({
342
+ server: async ({ config }, { onCleanup }) => {
343
+ const srv = await startServer(config);
344
+ onCleanup(() => srv.close());
345
+ return srv;
346
+ },
347
+ });
348
+
349
+ myTest("should connect to server", ({ server }) => {
350
+ expect(server.isRunning()).toBe(true);
351
+ });
352
+ ```
353
+
354
+ ### Detect Async Leaks
355
+
356
+ Reports leaked async resources with source locations. Enable while debugging:
357
+
358
+ ```typescript
359
+ test: {
360
+ detectAsyncLeaks: true, // Disable in CI (adds overhead)
361
+ }
362
+ ```
363
+
364
+ ## Performance
365
+
366
+ ### Pool Configuration
367
+
368
+ | Pool | Best For |
369
+ |---|---|
370
+ | `threads` (default) | General use, SolidJS + JSDOM tests |
371
+ | `forks` | Better isolation, more overhead |
372
+ | `vmThreads` | VM contexts with worker parallelism |
373
+
374
+ ### Sharding for CI
375
+
376
+ Split tests across CI machines:
377
+
378
+ ```bash
379
+ # Machine 1
380
+ vitest run --shard=1/3 --reporter=blob
381
+
382
+ # Machine 2
383
+ vitest run --shard=2/3 --reporter=blob
384
+
385
+ # Machine 3
386
+ vitest run --shard=3/3 --reporter=blob
387
+
388
+ # Merge
389
+ vitest merge-reports
390
+ ```
391
+
392
+ ### Other Tips
393
+
394
+ - Use `test.concurrent` for independent async tests
395
+ - Keep test files small and focused (Vitest parallelizes at the file level)
396
+ - Avoid heavy setup in `beforeAll` that could be lazy-loaded
397
+ - Use `--no-isolate` only for pure function test files, never for component tests
398
+
399
+ ## Recommended Config
400
+
401
+ ```typescript
402
+ // vite.config.ts (test section)
403
+ test: {
404
+ environment: "jsdom",
405
+ globals: true,
406
+ setupFiles: ["./tests/setup.ts"],
407
+ include: ["src/**/*.test.{ts,tsx}", "tests/**/*.test.{ts,tsx}"],
408
+ restoreMocks: true,
409
+ coverage: {
410
+ provider: "v8",
411
+ include: ["src/**/*.{ts,tsx}"],
412
+ exclude: [
413
+ "src/**/*.css.ts",
414
+ "src/**/index.ts",
415
+ "src/**/*.d.ts",
416
+ "src/locales/**",
417
+ ],
418
+ reporter: ["text", "html", "lcov"],
419
+ thresholds: {
420
+ statements: 80,
421
+ branches: 70,
422
+ functions: 80,
423
+ lines: 80,
424
+ },
425
+ },
426
+ }
427
+ ```
428
+
429
+ ```typescript
430
+ // tests/setup.ts
431
+ import "@testing-library/jest-dom/vitest";
432
+
433
+ afterEach(() => {
434
+ vi.clearAllTimers();
435
+ });
436
+ ```
437
+
438
+ ## Anti-Patterns
439
+
440
+ | Anti-Pattern | Fix |
441
+ |---|---|
442
+ | Testing implementation details (internal state, private methods) | Test behavior and public API only |
443
+ | Large snapshots (100+ lines) | Break into small focused snapshots or use explicit assertions |
444
+ | Blind `--update` on snapshot failures | Review each diff individually |
445
+ | `vi.mock` without cleanup | Enable `restoreMocks: true` in config |
446
+ | Shared mutable state across tests | Reset state in `beforeEach` or use fixtures |
447
+ | Testing framework code (that SolidJS reactivity works) | Test your logic, not the framework |
448
+ | `setTimeout` in tests for async waits | Use `waitFor`, `findBy*`, or `vi.advanceTimersByTime` |
449
+ | Over-mocking (mocking everything) | Mock only external boundaries (network, timers, platform APIs) |
450
+ | Destructuring SolidJS props in tests | Same rule as production -- never destructure props |
451
+ | Using `as any` in test code | If you need it, the API under test is wrong |
@@ -0,0 +1,179 @@
1
+ # Playwright E2E Patterns
2
+
3
+ > Playwright E2E testing with Page Objects, fixtures, auth, network mocking, and CI/CD.
4
+
5
+ **Type:** Stack Skill (requires `playwright` stack)
6
+ **Source:** [`stacks/playwright/skills/ct-playwright-patterns/SKILL.md`](../../stacks/playwright/skills/ct-playwright-patterns/SKILL.md)
7
+ **Directory Mappings:** `e2e/`, `playwright.config.ts`
8
+ **File Extensions:** `.spec.ts`
9
+
10
+ ## Overview
11
+
12
+ E2E tests sit at the top of the testing pyramid. They exercise full user journeys in real browsers. Write fewer, make each one count.
13
+
14
+ ## Project Structure
15
+
16
+ ```
17
+ e2e/
18
+ fixtures/ # Custom Playwright fixtures
19
+ index.ts # Extended test with page objects
20
+ pages/ # Page Object classes
21
+ components/ # Component Object classes (reusable fragments)
22
+ specs/ # Test files organized by feature
23
+ helpers/ # Utilities (data factories, API helpers)
24
+ .auth/ # storageState files (gitignored)
25
+ ```
26
+
27
+ ## Page Object Model + Fixtures
28
+
29
+ Combine Page Objects with custom fixtures. Tests receive pre-built page objects:
30
+
31
+ ```typescript
32
+ // e2e/pages/LoginPage.ts
33
+ export class LoginPage {
34
+ private readonly emailInput: Locator;
35
+ private readonly submitButton: Locator;
36
+
37
+ constructor(private readonly page: Page) {
38
+ this.emailInput = page.getByLabel("Email");
39
+ this.submitButton = page.getByRole("button", { name: "Sign in" });
40
+ }
41
+
42
+ async goto() { await this.page.goto("/login"); }
43
+ async login(email: string, password: string) {
44
+ await this.emailInput.fill(email);
45
+ await this.submitButton.click();
46
+ }
47
+ }
48
+
49
+ // e2e/fixtures/index.ts
50
+ export const test = base.extend<Fixtures>({
51
+ loginPage: async ({ page }, use) => { await use(new LoginPage(page)); },
52
+ });
53
+ ```
54
+
55
+ Fixtures are lazy (created on demand) and composable (can depend on each other).
56
+
57
+ ## Authentication
58
+
59
+ Use project dependencies with `storageState` to authenticate once and reuse:
60
+
61
+ ```typescript
62
+ // e2e/auth.setup.ts
63
+ setup("authenticate", async ({ page }) => {
64
+ await page.goto("/login");
65
+ await page.getByLabel("Email").fill("test@example.com");
66
+ await page.getByRole("button", { name: "Sign in" }).click();
67
+ await page.waitForURL("/dashboard");
68
+ await page.context().storageState({ path: "e2e/.auth/user.json" });
69
+ });
70
+ ```
71
+
72
+ Config: `dependencies: ["setup"]` + `storageState: "e2e/.auth/user.json"`. Add `e2e/.auth/` to `.gitignore`.
73
+
74
+ For multi-role testing, create separate setup files per role.
75
+
76
+ ## Locator Priority
77
+
78
+ 1. `getByRole()` -- accessibility semantics, survives refactors
79
+ 2. `getByLabel()` -- form elements
80
+ 3. `getByPlaceholder()` -- input hints
81
+ 4. `getByText()` -- visible text
82
+ 5. `getByTestId()` -- stable but less semantic
83
+ 6. CSS/XPath -- **avoid**
84
+
85
+ ## Web-First Assertions
86
+
87
+ Always use auto-retrying assertions:
88
+
89
+ ```typescript
90
+ // GOOD
91
+ await expect(page.getByRole("alert")).toBeVisible();
92
+ await expect(page).toHaveURL("/dashboard");
93
+
94
+ // BAD -- checks once, no retry
95
+ expect(await page.getByRole("alert").isVisible()).toBeTruthy();
96
+ ```
97
+
98
+ Key assertions: `toBeVisible()`, `toBeEnabled()`, `toHaveText()`, `toHaveURL()`, `toHaveCount()`, `toHaveScreenshot()`, `toMatchAriaSnapshot()`.
99
+
100
+ ## Network Mocking
101
+
102
+ ```typescript
103
+ // Mock API
104
+ await page.route("**/api/users", async (route) => {
105
+ await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify([{ name: "Alice" }]) });
106
+ });
107
+
108
+ // Block analytics
109
+ await page.route("**/analytics/**", (route) => route.abort());
110
+ ```
111
+
112
+ Mock ~80% of API calls for speed; keep ~20% hitting real endpoints. Always call `route.continue()`, `route.fulfill()`, or `route.abort()`. Use `page.unrouteAll()` in cleanup.
113
+
114
+ ## WebSocket Mocking (v1.53+)
115
+
116
+ ```typescript
117
+ await page.routeWebSocket("wss://example.com/ws", (ws) => {
118
+ ws.onMessage((message) => { ws.send("mocked response"); });
119
+ });
120
+ ```
121
+
122
+ ## Accessibility Testing
123
+
124
+ ```typescript
125
+ import AxeBuilder from "@axe-core/playwright";
126
+
127
+ test("WCAG 2.1 AA", async ({ page }) => {
128
+ await page.goto("/");
129
+ const results = await new AxeBuilder({ page }).withTags(["wcag2a", "wcag2aa", "wcag21aa"]).analyze();
130
+ expect(results.violations).toEqual([]);
131
+ });
132
+ ```
133
+
134
+ Aria snapshots (v1.52+): `await expect(nav).toMatchAriaSnapshot(...)`.
135
+
136
+ ## Parallelization
137
+
138
+ - File-level parallel (default) -- start here
139
+ - In-file: `test.describe.configure({ mode: "parallel" })`
140
+ - Full: `fullyParallel: true` in config
141
+
142
+ Each worker gets its own `BrowserContext` (isolated cookies/storage). Use `mode: "serial"` sparingly.
143
+
144
+ ## CI/CD
145
+
146
+ - Use official Playwright Docker image (`mcr.microsoft.com/playwright:v1.58.0-noble`)
147
+ - Upload reports + traces as artifacts
148
+ - Use `--shard` for suites exceeding 5 minutes
149
+ - `retries: process.env.CI ? 2 : 0`
150
+ - Traces: `trace: "on-first-retry"`
151
+
152
+ ## Flaky Test Prevention
153
+
154
+ | Cause | Fix |
155
+ | ---------------------- | ------------------------------------------------ |
156
+ | `waitForTimeout()` | Web-first assertions or `waitForResponse` |
157
+ | Brittle selectors | `getByRole`, `getByLabel`, `getByTestId` |
158
+ | Shared state | Fresh `BrowserContext` per test |
159
+ | External API flakiness | Mock with `page.route()` |
160
+ | Animation timing | `--force-prefers-reduced-motion` |
161
+ | Missing `await` | #1 source of false-passing tests. Lint for it. |
162
+
163
+ ## Anti-Patterns
164
+
165
+ 1. **Using `waitForTimeout()`** -- Use web-first assertions or explicit wait conditions.
166
+ 2. **Fragile CSS/XPath selectors** -- Use role-based and semantic locators.
167
+ 3. **Using `{ force: true }`** -- Masks real actionability problems.
168
+ 4. **Missing `await` on assertions** -- #1 source of false-passing tests.
169
+ 5. **Tests without assertions** -- A test that clicks without asserting is just a script.
170
+ 6. **Using Playwright for unit tests** -- Use Vitest for pure logic.
171
+ 7. **Committing raw codegen output** -- Always refactor into Page Objects.
172
+ 8. **Not cleaning up route handlers** -- Use `page.unrouteAll()`.
173
+
174
+ ## See Also
175
+
176
+ - [ct-testing-patterns](../skills/testing-patterns.md) -- Framework-agnostic TDD practices
177
+ - [Playwright E2E Best Practices](../best-practices/testing/playwright-e2e.md) -- SolidJS-specific E2E patterns
178
+ - [ct-vite-vitest-patterns](vite-vitest-patterns.md) -- Unit testing layer
179
+ - [ct-storybook-patterns](storybook-patterns.md) -- Interaction testing layer