@martinffx/atelier 2.0.0 → 3.0.1

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.
Files changed (81) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +94 -253
  3. package/package.json +1 -1
  4. package/skills/{spec-orchestrator → atelier-orchestrator}/SKILL.md +3 -3
  5. package/skills/atelier-setup/SKILL.md +80 -0
  6. package/skills/code-review/references/reviewers.md +3 -3
  7. package/skills/code-review/references/rq.md +6 -6
  8. package/skills/spec-brainstorm/SKILL.md +5 -5
  9. package/skills/spec-implement/SKILL.md +2 -3
  10. package/skills/spec-plan/SKILL.md +21 -29
  11. package/skills/spec-plan/references/plan_template.md +77 -0
  12. package/skills/python-architecture/SKILL.md +0 -148
  13. package/skills/python-architecture/references/data-modeling.md +0 -558
  14. package/skills/python-architecture/references/ddd.md +0 -569
  15. package/skills/python-architecture/references/functional-core.md +0 -337
  16. package/skills/python-build-tools/SKILL.md +0 -153
  17. package/skills/python-build-tools/references/basedpyright.md +0 -321
  18. package/skills/python-build-tools/references/ruff.md +0 -225
  19. package/skills/python-build-tools/references/uv.md +0 -257
  20. package/skills/python-fastapi/SKILL.md +0 -184
  21. package/skills/python-fastapi/references/api-design.md +0 -563
  22. package/skills/python-fastapi/references/dependencies.md +0 -479
  23. package/skills/python-fastapi/references/middleware.md +0 -475
  24. package/skills/python-fastapi/references/validation.md +0 -497
  25. package/skills/python-modern-python/SKILL.md +0 -201
  26. package/skills/python-modern-python/references/async.md +0 -76
  27. package/skills/python-modern-python/references/pattern-matching.md +0 -89
  28. package/skills/python-modern-python/references/typing.md +0 -96
  29. package/skills/python-monorepo/SKILL.md +0 -146
  30. package/skills/python-monorepo/references/docker.md +0 -515
  31. package/skills/python-monorepo/references/namespace-packages.md +0 -426
  32. package/skills/python-monorepo/references/workspace-config.md +0 -341
  33. package/skills/python-sqlalchemy/SKILL.md +0 -119
  34. package/skills/python-sqlalchemy/references/async.md +0 -51
  35. package/skills/python-sqlalchemy/references/models.md +0 -67
  36. package/skills/python-sqlalchemy/references/queries.md +0 -57
  37. package/skills/python-temporal/SKILL.md +0 -128
  38. package/skills/python-temporal/references/patterns.md +0 -87
  39. package/skills/python-temporal/references/testing.md +0 -54
  40. package/skills/python-testing/SKILL.md +0 -161
  41. package/skills/python-testing/references/boundaries.md +0 -449
  42. package/skills/python-testing/references/mocking.md +0 -449
  43. package/skills/python-testing/references/pytest.md +0 -524
  44. package/skills/typescript-api-design/SKILL.md +0 -396
  45. package/skills/typescript-api-design/references/error-responses.md +0 -469
  46. package/skills/typescript-build-tools/SKILL.md +0 -447
  47. package/skills/typescript-build-tools/references/biome.md +0 -436
  48. package/skills/typescript-build-tools/references/bun.md +0 -432
  49. package/skills/typescript-build-tools/references/turborepo.md +0 -426
  50. package/skills/typescript-build-tools/references/vitest.md +0 -518
  51. package/skills/typescript-drizzle-orm/SKILL.md +0 -398
  52. package/skills/typescript-drizzle-orm/references/cloudflare.md +0 -392
  53. package/skills/typescript-drizzle-orm/references/entity-pattern.md +0 -412
  54. package/skills/typescript-drizzle-orm/references/postgresql.md +0 -191
  55. package/skills/typescript-drizzle-orm/references/repository-pattern.md +0 -479
  56. package/skills/typescript-drizzle-orm/references/sqlite.md +0 -291
  57. package/skills/typescript-dynamodb-toolbox/SKILL.md +0 -370
  58. package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +0 -574
  59. package/skills/typescript-dynamodb-toolbox/references/error-handling.md +0 -495
  60. package/skills/typescript-dynamodb-toolbox/references/modeling.md +0 -332
  61. package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +0 -631
  62. package/skills/typescript-dynamodb-toolbox/references/testing.md +0 -718
  63. package/skills/typescript-dynamodb-toolbox/references/transactions.md +0 -423
  64. package/skills/typescript-effect-ts/SKILL.md +0 -341
  65. package/skills/typescript-effect-ts/references/core.md +0 -412
  66. package/skills/typescript-effect-ts/references/error-handling.md +0 -468
  67. package/skills/typescript-effect-ts/references/resources.md +0 -476
  68. package/skills/typescript-effect-ts/references/schema.md +0 -536
  69. package/skills/typescript-effect-ts/references/services.md +0 -461
  70. package/skills/typescript-fastify/SKILL.md +0 -405
  71. package/skills/typescript-fastify/references/plugins.md +0 -329
  72. package/skills/typescript-fastify/references/typeid.md +0 -315
  73. package/skills/typescript-functional-patterns/SKILL.md +0 -402
  74. package/skills/typescript-functional-patterns/references/adts.md +0 -497
  75. package/skills/typescript-functional-patterns/references/branded-types.md +0 -542
  76. package/skills/typescript-functional-patterns/references/migration-guide.md +0 -695
  77. package/skills/typescript-functional-patterns/references/option-result.md +0 -585
  78. package/skills/typescript-testing/SKILL.md +0 -353
  79. package/skills/typescript-testing/references/mocking.md +0 -675
  80. package/skills/typescript-testing/references/msw.md +0 -852
  81. package/skills/typescript-testing/references/snapshot-testing.md +0 -774
@@ -1,774 +0,0 @@
1
- # Snapshot Testing Patterns
2
-
3
- Comprehensive guide to snapshot testing with Vitest for validating complex objects, UI output, and generated code.
4
-
5
- ## Basic Snapshot Testing
6
-
7
- ### First Snapshot
8
-
9
- ```typescript
10
- import { it, expect } from 'vitest'
11
-
12
- it('generates user profile', () => {
13
- const profile = {
14
- id: '123',
15
- name: 'Alice',
16
- email: 'alice@example.com',
17
- role: 'admin',
18
- createdAt: new Date('2024-01-01'),
19
- }
20
-
21
- // Creates snapshot file on first run
22
- expect(profile).toMatchSnapshot()
23
- })
24
- ```
25
-
26
- First run creates: `__snapshots__/test-file.spec.ts.snap`
27
-
28
- ```typescript
29
- // Vitest Snapshot v1
30
-
31
- exports[`generates user profile 1`] = `
32
- {
33
- "id": "123",
34
- "name": "Alice",
35
- "email": "alice@example.com",
36
- "role": "admin",
37
- "createdAt": 2024-01-01T00:00:00.000Z,
38
- }
39
- `
40
- ```
41
-
42
- ### Snapshot Assertions
43
-
44
- ```typescript
45
- // Basic snapshot
46
- expect(value).toMatchSnapshot()
47
-
48
- // Named snapshot (multiple in one test)
49
- expect(value1).toMatchSnapshot('first snapshot')
50
- expect(value2).toMatchSnapshot('second snapshot')
51
-
52
- // Inline snapshot
53
- expect(value).toMatchInlineSnapshot(`
54
- {
55
- "id": "123",
56
- "name": "Alice",
57
- }
58
- `)
59
-
60
- // File snapshot (for large output)
61
- expect(generatedCode).toMatchFileSnapshot('__snapshots__/output.txt')
62
- ```
63
-
64
- ## Inline Snapshots
65
-
66
- ### Basic Inline Snapshot
67
-
68
- ```typescript
69
- it('formats error message', () => {
70
- const error = formatError(new Error('Something failed'))
71
-
72
- expect(error).toMatchInlineSnapshot(`
73
- {
74
- "message": "Something failed",
75
- "code": "UNKNOWN_ERROR",
76
- "timestamp": 1234567890,
77
- }
78
- `)
79
- })
80
- ```
81
-
82
- Vitest automatically writes the snapshot inline on first run.
83
-
84
- ### Benefits of Inline Snapshots
85
-
86
- - See expected value directly in test code
87
- - No separate snapshot file to manage
88
- - Great for small, focused snapshots
89
- - Easier to review in pull requests
90
- - Better for objects with few properties
91
-
92
- ### When to Use Inline vs File Snapshots
93
-
94
- ```typescript
95
- // INLINE - Small, focused objects
96
- expect(user.toJSON()).toMatchInlineSnapshot(`
97
- {
98
- "id": "123",
99
- "name": "Alice",
100
- }
101
- `)
102
-
103
- // FILE - Large objects or complex structures
104
- expect(entireApiResponse).toMatchSnapshot()
105
-
106
- // FILE - Generated code or markup
107
- expect(generatedHTML).toMatchSnapshot()
108
- ```
109
-
110
- ## Property Matchers
111
-
112
- ### Handling Dynamic Values
113
-
114
- ```typescript
115
- it('creates order with timestamp', () => {
116
- const order = createOrder({
117
- items: ['item1', 'item2'],
118
- })
119
-
120
- // Without property matchers - BREAKS on every run!
121
- // expect(order).toMatchSnapshot() ❌
122
-
123
- // With property matchers - handles dynamic values
124
- expect(order).toMatchSnapshot({
125
- id: expect.any(String),
126
- createdAt: expect.any(Date),
127
- })
128
- })
129
- ```
130
-
131
- Snapshot file shows matchers:
132
-
133
- ```typescript
134
- exports[`creates order 1`] = `
135
- {
136
- "id": Any<String>,
137
- "createdAt": Any<Date>,
138
- "items": ["item1", "item2"],
139
- "status": "pending",
140
- }
141
- `
142
- ```
143
-
144
- ### Common Property Matchers
145
-
146
- ```typescript
147
- expect(object).toMatchSnapshot({
148
- // Type matchers
149
- id: expect.any(String),
150
- count: expect.any(Number),
151
- active: expect.any(Boolean),
152
- createdAt: expect.any(Date),
153
- data: expect.any(Object),
154
- tags: expect.any(Array),
155
-
156
- // Pattern matchers
157
- email: expect.stringMatching(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/),
158
- uuid: expect.stringMatching(/^[0-9a-f]{8}-[0-9a-f]{4}-/),
159
-
160
- // Range matchers
161
- price: expect.closeTo(19.99, 0.01),
162
-
163
- // Object matchers
164
- user: expect.objectContaining({
165
- name: 'Alice',
166
- }),
167
-
168
- // Array matchers
169
- tags: expect.arrayContaining(['typescript', 'testing']),
170
- })
171
- ```
172
-
173
- ### Nested Property Matchers
174
-
175
- ```typescript
176
- it('handles nested dynamic values', () => {
177
- const response = {
178
- data: {
179
- user: {
180
- id: crypto.randomUUID(),
181
- name: 'Alice',
182
- createdAt: new Date(),
183
- },
184
- },
185
- meta: {
186
- requestId: crypto.randomUUID(),
187
- timestamp: Date.now(),
188
- },
189
- }
190
-
191
- expect(response).toMatchSnapshot({
192
- data: {
193
- user: {
194
- id: expect.any(String),
195
- createdAt: expect.any(Date),
196
- },
197
- },
198
- meta: {
199
- requestId: expect.any(String),
200
- timestamp: expect.any(Number),
201
- },
202
- })
203
- })
204
- ```
205
-
206
- ## Updating Snapshots
207
-
208
- ### Update All Snapshots
209
-
210
- ```bash
211
- # Update all outdated snapshots
212
- vitest -u
213
-
214
- # or
215
- vitest --update
216
- ```
217
-
218
- ### Update Specific Snapshots
219
-
220
- ```bash
221
- # Run specific test file and update
222
- vitest path/to/test.spec.ts -u
223
-
224
- # Update only tests matching pattern
225
- vitest -t "user profile" -u
226
- ```
227
-
228
- ### Interactive Update
229
-
230
- ```bash
231
- # Watch mode with interactive updates
232
- vitest --ui
233
-
234
- # Press 'u' to update failing snapshots
235
- # Press 'i' to update snapshot interactively
236
- ```
237
-
238
- ### When to Update Snapshots
239
-
240
- ```typescript
241
- // ✅ GOOD REASONS to update:
242
- // - Intentional behavior change
243
- // - New feature added
244
- // - Bug fix that changes output
245
- // - Refactoring that changes structure
246
-
247
- // ❌ BAD REASONS to update:
248
- // - Test is failing and you don't know why
249
- // - You didn't review the diff
250
- // - To make CI pass quickly
251
- // - Random non-deterministic changes
252
- ```
253
-
254
- ## File Snapshots
255
-
256
- ### Basic File Snapshot
257
-
258
- ```typescript
259
- import { it, expect } from 'vitest'
260
- import { generateComponent } from './generator'
261
-
262
- it('generates React component', () => {
263
- const code = generateComponent({
264
- name: 'UserProfile',
265
- props: ['name', 'email'],
266
- })
267
-
268
- expect(code).toMatchFileSnapshot('__snapshots__/UserProfile.tsx')
269
- })
270
- ```
271
-
272
- Creates: `__snapshots__/UserProfile.tsx`
273
-
274
- ```typescript
275
- import React from 'react'
276
-
277
- interface UserProfileProps {
278
- name: string
279
- email: string
280
- }
281
-
282
- export function UserProfile({ name, email }: UserProfileProps) {
283
- return (
284
- <div>
285
- <h1>{name}</h1>
286
- <p>{email}</p>
287
- </div>
288
- )
289
- }
290
- ```
291
-
292
- ### Custom Snapshot Directory
293
-
294
- ```typescript
295
- // Organize snapshots by feature
296
- expect(output).toMatchFileSnapshot('./__snapshots__/feature/output.txt')
297
-
298
- // Keep snapshots close to tests
299
- expect(output).toMatchFileSnapshot('./snapshots/expected-output.txt')
300
- ```
301
-
302
- ### Multiple File Snapshots
303
-
304
- ```typescript
305
- it('generates multiple files', () => {
306
- const files = generateProject({ name: 'my-app' })
307
-
308
- expect(files.index).toMatchFileSnapshot('snapshots/index.tsx')
309
- expect(files.config).toMatchFileSnapshot('snapshots/config.json')
310
- expect(files.readme).toMatchFileSnapshot('snapshots/README.md')
311
- })
312
- ```
313
-
314
- ## Custom Serializers
315
-
316
- ### Basic Custom Serializer
317
-
318
- ```typescript
319
- import { expect } from 'vitest'
320
-
321
- // Custom serializer for Date objects
322
- expect.addSnapshotSerializer({
323
- test: (val) => val instanceof Date,
324
- serialize: (val: Date) => `Date<${val.toISOString()}>`,
325
- })
326
-
327
- // Now dates serialize consistently
328
- expect({ createdAt: new Date('2024-01-01') }).toMatchInlineSnapshot(`
329
- {
330
- "createdAt": Date<2024-01-01T00:00:00.000Z>,
331
- }
332
- `)
333
- ```
334
-
335
- ### Complex Serializer Example
336
-
337
- ```typescript
338
- // Serializer for BigInt
339
- expect.addSnapshotSerializer({
340
- test: (val) => typeof val === 'bigint',
341
- serialize: (val: bigint) => `${val.toString()}n`,
342
- })
343
-
344
- // Serializer for class instances
345
- class User {
346
- constructor(public name: string, public email: string) {}
347
- }
348
-
349
- expect.addSnapshotSerializer({
350
- test: (val) => val instanceof User,
351
- serialize: (val: User) => {
352
- return `User { name: ${val.name}, email: ${val.email} }`
353
- },
354
- })
355
-
356
- // Usage
357
- const user = new User('Alice', 'alice@example.com')
358
- expect(user).toMatchInlineSnapshot(`User { name: Alice, email: alice@example.com }`)
359
- ```
360
-
361
- ### Setup Serializers Globally
362
-
363
- ```typescript
364
- // tests/setup.ts
365
- import { expect, beforeAll } from 'vitest'
366
-
367
- beforeAll(() => {
368
- // Add all custom serializers
369
- expect.addSnapshotSerializer({
370
- test: (val) => val instanceof Date,
371
- serialize: (val: Date) => `Date<${val.toISOString()}>`,
372
- })
373
-
374
- expect.addSnapshotSerializer({
375
- test: (val) => typeof val === 'bigint',
376
- serialize: (val: bigint) => `${val.toString()}n`,
377
- })
378
- })
379
- ```
380
-
381
- ## Best Practices
382
-
383
- ### Snapshot Size
384
-
385
- ```typescript
386
- // ✅ GOOD - Focused snapshot
387
- it('formats user display name', () => {
388
- const display = formatDisplayName(user)
389
- expect(display).toMatchInlineSnapshot(`"Alice (alice@example.com)"`)
390
- })
391
-
392
- // ❌ BAD - Too large, hard to review
393
- it('returns entire API response', async () => {
394
- const response = await api.getEverything()
395
- expect(response).toMatchSnapshot() // 500+ lines
396
- })
397
-
398
- // ✅ BETTER - Snapshot specific parts
399
- it('returns correct user data structure', async () => {
400
- const response = await api.getUsers()
401
- expect(response.users[0]).toMatchSnapshot({
402
- id: expect.any(String),
403
- createdAt: expect.any(Date),
404
- })
405
- })
406
- ```
407
-
408
- ### When to Use Snapshots
409
-
410
- ```typescript
411
- // ✅ GOOD USE CASES:
412
- // - Generated code or markup
413
- // - Complex nested objects
414
- // - API response structures
415
- // - Error message formats
416
- // - Configuration objects
417
-
418
- it('generates SQL query', () => {
419
- const query = buildQuery({ table: 'users', where: { active: true } })
420
- expect(query).toMatchInlineSnapshot(`
421
- SELECT * FROM users
422
- WHERE active = true
423
- `)
424
- })
425
-
426
- // ❌ BAD USE CASES:
427
- // - Simple primitives
428
- // - Single boolean/number assertions
429
- // - Critical business logic
430
-
431
- // Bad - too simple for snapshot
432
- it('returns user count', () => {
433
- expect(users.length).toMatchInlineSnapshot(`5`) // Just use toBe(5)
434
- })
435
-
436
- // Bad - critical logic needs explicit assertion
437
- it('calculates correct total', () => {
438
- expect(calculateTotal(cart)).toMatchInlineSnapshot(`19.99`)
439
- // Should use: expect(calculateTotal(cart)).toBe(19.99)
440
- })
441
- ```
442
-
443
- ### Explicit vs Snapshot Assertions
444
-
445
- ```typescript
446
- // ✅ CRITICAL VALUES - Use explicit assertions
447
- it('charges correct amount', () => {
448
- const charge = processPayment(order)
449
- expect(charge.amount).toBe(19.99) // Explicit - catches regressions
450
- expect(charge.currency).toBe('USD')
451
- })
452
-
453
- // ✅ STRUCTURE - Use snapshots
454
- it('returns payment receipt structure', () => {
455
- const receipt = processPayment(order)
456
- expect(receipt).toMatchSnapshot({
457
- transactionId: expect.any(String),
458
- timestamp: expect.any(Date),
459
- })
460
- })
461
- ```
462
-
463
- ## Testing React Components
464
-
465
- ### Component Snapshot
466
-
467
- ```typescript
468
- import { render } from '@testing-library/react'
469
- import { UserProfile } from './UserProfile'
470
-
471
- it('renders user profile', () => {
472
- const { container } = render(
473
- <UserProfile name="Alice" email="alice@example.com" />
474
- )
475
-
476
- expect(container.firstChild).toMatchInlineSnapshot(`
477
- <div class="profile">
478
- <h1>Alice</h1>
479
- <p>alice@example.com</p>
480
- </div>
481
- `)
482
- })
483
- ```
484
-
485
- ### Snapshot Query Results
486
-
487
- ```typescript
488
- it('shows search results', () => {
489
- const { getByRole } = render(<SearchResults query="react" />)
490
-
491
- expect(getByRole('list')).toMatchSnapshot()
492
- })
493
- ```
494
-
495
- ### Avoid Snapshot Testing UI Too Much
496
-
497
- ```typescript
498
- // ❌ BAD - Brittle, breaks on style changes
499
- it('renders button', () => {
500
- const { container } = render(<Button>Click me</Button>)
501
- expect(container).toMatchSnapshot()
502
- })
503
-
504
- // ✅ BETTER - Test behavior, not structure
505
- it('calls onClick when clicked', () => {
506
- const onClick = vi.fn()
507
- const { getByText } = render(<Button onClick={onClick}>Click me</Button>)
508
-
509
- fireEvent.click(getByText('Click me'))
510
- expect(onClick).toHaveBeenCalledOnce()
511
- })
512
- ```
513
-
514
- ## Debugging Snapshots
515
-
516
- ### Viewing Snapshot Diffs
517
-
518
- ```bash
519
- # Run tests and see diff
520
- vitest
521
-
522
- # Vitest shows:
523
- # ✓ Expected
524
- # ✗ Received
525
- ```
526
-
527
- ### Example Diff Output
528
-
529
- ```diff
530
- - Expected
531
- + Received
532
-
533
- Object {
534
- "name": "Alice",
535
- - "email": "alice@example.com",
536
- + "email": "alice@newdomain.com",
537
- "role": "admin",
538
- }
539
- ```
540
-
541
- ### Debugging Tips
542
-
543
- ```typescript
544
- // 1. Use console.log to inspect value
545
- it('generates config', () => {
546
- const config = generateConfig()
547
- console.log(JSON.stringify(config, null, 2))
548
- expect(config).toMatchSnapshot()
549
- })
550
-
551
- // 2. Use snapshot with property matchers
552
- expect(value).toMatchSnapshot({
553
- id: expect.any(String), // Ignore dynamic ID
554
- })
555
-
556
- // 3. Break down large snapshots
557
- it('has correct structure', () => {
558
- expect(response.user).toMatchSnapshot()
559
- expect(response.meta).toMatchSnapshot()
560
- expect(response.data).toMatchSnapshot()
561
- })
562
- ```
563
-
564
- ## Common Patterns
565
-
566
- ### Testing Transformations
567
-
568
- ```typescript
569
- it('transforms API response to UI format', () => {
570
- const apiResponse = {
571
- user_id: '123',
572
- user_name: 'Alice',
573
- created_at: '2024-01-01T00:00:00Z',
574
- }
575
-
576
- const uiFormat = transformToUI(apiResponse)
577
-
578
- expect(uiFormat).toMatchInlineSnapshot(`
579
- {
580
- "id": "123",
581
- "name": "Alice",
582
- "createdAt": "Jan 1, 2024",
583
- }
584
- `)
585
- })
586
- ```
587
-
588
- ### Testing Error Messages
589
-
590
- ```typescript
591
- it('formats validation errors', () => {
592
- const errors = validateUser({
593
- name: '',
594
- email: 'invalid',
595
- })
596
-
597
- expect(errors).toMatchInlineSnapshot(`
598
- [
599
- {
600
- "field": "name",
601
- "message": "Name is required",
602
- },
603
- {
604
- "field": "email",
605
- "message": "Invalid email format",
606
- },
607
- ]
608
- `)
609
- })
610
- ```
611
-
612
- ### Testing Generated Code
613
-
614
- ```typescript
615
- it('generates TypeScript interface', () => {
616
- const code = generateInterface({
617
- name: 'User',
618
- properties: {
619
- id: 'string',
620
- name: 'string',
621
- age: 'number',
622
- },
623
- })
624
-
625
- expect(code).toMatchInlineSnapshot(`
626
- "interface User {
627
- id: string
628
- name: string
629
- age: number
630
- }"
631
- `)
632
- })
633
- ```
634
-
635
- ### Testing API Contracts
636
-
637
- ```typescript
638
- it('returns correct API response format', async () => {
639
- const response = await api.getUser('123')
640
-
641
- expect(response).toMatchSnapshot({
642
- id: expect.any(String),
643
- createdAt: expect.any(String),
644
- updatedAt: expect.any(String),
645
- })
646
- })
647
- ```
648
-
649
- ## Guidelines
650
-
651
- 1. **Review snapshot diffs carefully** - Don't blindly update failing snapshots
652
- 2. **Use property matchers for dynamic values** - Dates, IDs, timestamps should use `expect.any()`
653
- 3. **Keep snapshots focused and small** - Large snapshots are hard to review and maintain
654
- 4. **Inline for small objects, file for large** - Use inline snapshots for objects < 10 lines
655
- 5. **Explicit assertions for critical values** - Don't snapshot business-critical calculations
656
- 6. **Named snapshots for multiple per test** - Use descriptive names: `.toMatchSnapshot('error state')`
657
- 7. **Custom serializers for consistency** - Add serializers for Date, BigInt, class instances
658
- 8. **Update intentionally, not reactively** - Understand why snapshot changed before updating
659
- 9. **Commit snapshot files** - Snapshots are part of your test suite
660
- 10. **Avoid snapshotting UI structure** - Test behavior, not implementation details
661
- 11. **Use snapshots for regression testing** - Great for ensuring output doesn't change unexpectedly
662
- 12. **Document non-obvious snapshots** - Add comments explaining complex snapshot expectations
663
-
664
- ## Common Pitfalls
665
-
666
- ### Non-Deterministic Snapshots
667
-
668
- ```typescript
669
- // ❌ BAD - Fails randomly due to Date.now()
670
- it('creates timestamp', () => {
671
- const data = { timestamp: Date.now() }
672
- expect(data).toMatchSnapshot() // Different every time!
673
- })
674
-
675
- // ✅ GOOD - Use property matcher
676
- it('creates timestamp', () => {
677
- const data = { timestamp: Date.now() }
678
- expect(data).toMatchSnapshot({
679
- timestamp: expect.any(Number),
680
- })
681
- })
682
- ```
683
-
684
- ### Snapshots Too Large
685
-
686
- ```typescript
687
- // ❌ BAD - 500 line snapshot
688
- it('returns all data', async () => {
689
- const data = await fetchEverything()
690
- expect(data).toMatchSnapshot() // Unmaintainable
691
- })
692
-
693
- // ✅ GOOD - Focused snapshots
694
- it('returns user list structure', async () => {
695
- const data = await fetchEverything()
696
- expect(data.users[0]).toMatchSnapshot({
697
- id: expect.any(String),
698
- createdAt: expect.any(Date),
699
- })
700
- })
701
- ```
702
-
703
- ### Blindly Updating Snapshots
704
-
705
- ```typescript
706
- // ❌ BAD PROCESS:
707
- // 1. Test fails
708
- // 2. Run `vitest -u` without checking diff
709
- // 3. Commit updated snapshot
710
- // 4. Ship bug to production
711
-
712
- // ✅ GOOD PROCESS:
713
- // 1. Test fails
714
- // 2. Review snapshot diff carefully
715
- // 3. Determine if change is intentional
716
- // 4. If yes: update snapshot, if no: fix code
717
- // 5. Verify test passes with new snapshot
718
- ```
719
-
720
- ### Snapshotting Implementation Details
721
-
722
- ```typescript
723
- // ❌ BAD - Snapshots component internals
724
- it('renders user list', () => {
725
- const { container } = render(<UserList />)
726
- expect(container.innerHTML).toMatchSnapshot() // Breaks on any DOM change
727
- })
728
-
729
- // ✅ GOOD - Test user-facing behavior
730
- it('displays all users', () => {
731
- const { getByText } = render(<UserList />)
732
- expect(getByText('Alice')).toBeInTheDocument()
733
- expect(getByText('Bob')).toBeInTheDocument()
734
- })
735
- ```
736
-
737
- ## Snapshot Maintenance
738
-
739
- ### Regular Review
740
-
741
- ```bash
742
- # Check for outdated snapshots
743
- vitest --reporter=verbose
744
-
745
- # List all snapshot files
746
- find . -name "*.snap" -o -name "*.snap.txt"
747
-
748
- # Check snapshot file sizes
749
- find . -name "*.snap" -exec wc -l {} \; | sort -rn
750
- ```
751
-
752
- ### Removing Unused Snapshots
753
-
754
- ```bash
755
- # Vitest automatically removes unused snapshots
756
- vitest -u
757
-
758
- # Or manually delete snapshot file and re-run
759
- rm __snapshots__/old-test.spec.ts.snap
760
- vitest
761
- ```
762
-
763
- ### CI/CD Integration
764
-
765
- ```yaml
766
- # GitHub Actions example
767
- - name: Run tests
768
- run: vitest run
769
-
770
- # Fail if snapshots need updating
771
- - name: Check for snapshot changes
772
- run: |
773
- git diff --exit-code "**/*.snap"
774
- ```