@martinffx/atelier 1.4.0 → 3.0.0

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 (93) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +95 -220
  3. package/agents/architect.md +6 -6
  4. package/agents/oracle.md +1 -1
  5. package/agents/{recon.md → sentinel.md} +3 -3
  6. package/dist/atelier.js +167 -26
  7. package/package.json +2 -1
  8. package/skills/atelier-orchestrator/SKILL.md +180 -0
  9. package/skills/atelier-setup/SKILL.md +80 -0
  10. package/skills/code-review/SKILL.md +2 -2
  11. package/skills/code-review/references/reviewers.md +3 -3
  12. package/skills/code-review/references/rq.md +9 -9
  13. package/skills/code-subagents/SKILL.md +44 -75
  14. package/skills/code-subagents/references/batch-reviewer-prompt.md +44 -0
  15. package/skills/code-subagents/references/implementor-prompt.md +10 -11
  16. package/skills/spec-brainstorm/SKILL.md +21 -16
  17. package/skills/spec-finish/SKILL.md +14 -14
  18. package/skills/spec-implement/SKILL.md +42 -42
  19. package/skills/spec-plan/SKILL.md +72 -26
  20. package/skills/spec-plan/references/plan_template.md +77 -0
  21. package/skills/code-subagents/references/code-quality-reviewer-prompt.md +0 -48
  22. package/skills/code-subagents/references/spec-reviewer-prompt.md +0 -62
  23. package/skills/python-architecture/SKILL.md +0 -148
  24. package/skills/python-architecture/references/data-modeling.md +0 -558
  25. package/skills/python-architecture/references/ddd.md +0 -569
  26. package/skills/python-architecture/references/functional-core.md +0 -337
  27. package/skills/python-build-tools/SKILL.md +0 -153
  28. package/skills/python-build-tools/references/basedpyright.md +0 -321
  29. package/skills/python-build-tools/references/ruff.md +0 -225
  30. package/skills/python-build-tools/references/uv.md +0 -257
  31. package/skills/python-fastapi/SKILL.md +0 -184
  32. package/skills/python-fastapi/references/api-design.md +0 -563
  33. package/skills/python-fastapi/references/dependencies.md +0 -479
  34. package/skills/python-fastapi/references/middleware.md +0 -475
  35. package/skills/python-fastapi/references/validation.md +0 -497
  36. package/skills/python-modern-python/SKILL.md +0 -201
  37. package/skills/python-modern-python/references/async.md +0 -76
  38. package/skills/python-modern-python/references/pattern-matching.md +0 -89
  39. package/skills/python-modern-python/references/typing.md +0 -96
  40. package/skills/python-monorepo/SKILL.md +0 -146
  41. package/skills/python-monorepo/references/docker.md +0 -515
  42. package/skills/python-monorepo/references/namespace-packages.md +0 -426
  43. package/skills/python-monorepo/references/workspace-config.md +0 -341
  44. package/skills/python-sqlalchemy/SKILL.md +0 -119
  45. package/skills/python-sqlalchemy/references/async.md +0 -51
  46. package/skills/python-sqlalchemy/references/models.md +0 -67
  47. package/skills/python-sqlalchemy/references/queries.md +0 -57
  48. package/skills/python-temporal/SKILL.md +0 -128
  49. package/skills/python-temporal/references/patterns.md +0 -87
  50. package/skills/python-temporal/references/testing.md +0 -54
  51. package/skills/python-testing/SKILL.md +0 -161
  52. package/skills/python-testing/references/boundaries.md +0 -449
  53. package/skills/python-testing/references/mocking.md +0 -449
  54. package/skills/python-testing/references/pytest.md +0 -524
  55. package/skills/spec-orchestrator/SKILL.md +0 -145
  56. package/skills/typescript-api-design/SKILL.md +0 -396
  57. package/skills/typescript-api-design/references/error-responses.md +0 -469
  58. package/skills/typescript-build-tools/SKILL.md +0 -447
  59. package/skills/typescript-build-tools/references/biome.md +0 -436
  60. package/skills/typescript-build-tools/references/bun.md +0 -432
  61. package/skills/typescript-build-tools/references/turborepo.md +0 -426
  62. package/skills/typescript-build-tools/references/vitest.md +0 -518
  63. package/skills/typescript-drizzle-orm/SKILL.md +0 -398
  64. package/skills/typescript-drizzle-orm/references/cloudflare.md +0 -392
  65. package/skills/typescript-drizzle-orm/references/entity-pattern.md +0 -412
  66. package/skills/typescript-drizzle-orm/references/postgresql.md +0 -191
  67. package/skills/typescript-drizzle-orm/references/repository-pattern.md +0 -479
  68. package/skills/typescript-drizzle-orm/references/sqlite.md +0 -291
  69. package/skills/typescript-dynamodb-toolbox/SKILL.md +0 -370
  70. package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +0 -574
  71. package/skills/typescript-dynamodb-toolbox/references/error-handling.md +0 -495
  72. package/skills/typescript-dynamodb-toolbox/references/modeling.md +0 -332
  73. package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +0 -631
  74. package/skills/typescript-dynamodb-toolbox/references/testing.md +0 -718
  75. package/skills/typescript-dynamodb-toolbox/references/transactions.md +0 -423
  76. package/skills/typescript-effect-ts/SKILL.md +0 -341
  77. package/skills/typescript-effect-ts/references/core.md +0 -412
  78. package/skills/typescript-effect-ts/references/error-handling.md +0 -468
  79. package/skills/typescript-effect-ts/references/resources.md +0 -476
  80. package/skills/typescript-effect-ts/references/schema.md +0 -536
  81. package/skills/typescript-effect-ts/references/services.md +0 -461
  82. package/skills/typescript-fastify/SKILL.md +0 -405
  83. package/skills/typescript-fastify/references/plugins.md +0 -329
  84. package/skills/typescript-fastify/references/typeid.md +0 -315
  85. package/skills/typescript-functional-patterns/SKILL.md +0 -402
  86. package/skills/typescript-functional-patterns/references/adts.md +0 -497
  87. package/skills/typescript-functional-patterns/references/branded-types.md +0 -542
  88. package/skills/typescript-functional-patterns/references/migration-guide.md +0 -695
  89. package/skills/typescript-functional-patterns/references/option-result.md +0 -585
  90. package/skills/typescript-testing/SKILL.md +0 -353
  91. package/skills/typescript-testing/references/mocking.md +0 -675
  92. package/skills/typescript-testing/references/msw.md +0 -852
  93. 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
- ```