@martinffx/atelier 2.0.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.
- package/CHANGELOG.md +14 -0
- package/README.md +94 -253
- package/package.json +1 -1
- package/skills/{spec-orchestrator → atelier-orchestrator}/SKILL.md +3 -3
- package/skills/atelier-setup/SKILL.md +80 -0
- package/skills/code-review/references/reviewers.md +3 -3
- package/skills/code-review/references/rq.md +6 -6
- package/skills/spec-brainstorm/SKILL.md +5 -5
- package/skills/spec-implement/SKILL.md +2 -3
- package/skills/spec-plan/SKILL.md +21 -29
- package/skills/spec-plan/references/plan_template.md +77 -0
- package/skills/python-architecture/SKILL.md +0 -148
- package/skills/python-architecture/references/data-modeling.md +0 -558
- package/skills/python-architecture/references/ddd.md +0 -569
- package/skills/python-architecture/references/functional-core.md +0 -337
- package/skills/python-build-tools/SKILL.md +0 -153
- package/skills/python-build-tools/references/basedpyright.md +0 -321
- package/skills/python-build-tools/references/ruff.md +0 -225
- package/skills/python-build-tools/references/uv.md +0 -257
- package/skills/python-fastapi/SKILL.md +0 -184
- package/skills/python-fastapi/references/api-design.md +0 -563
- package/skills/python-fastapi/references/dependencies.md +0 -479
- package/skills/python-fastapi/references/middleware.md +0 -475
- package/skills/python-fastapi/references/validation.md +0 -497
- package/skills/python-modern-python/SKILL.md +0 -201
- package/skills/python-modern-python/references/async.md +0 -76
- package/skills/python-modern-python/references/pattern-matching.md +0 -89
- package/skills/python-modern-python/references/typing.md +0 -96
- package/skills/python-monorepo/SKILL.md +0 -146
- package/skills/python-monorepo/references/docker.md +0 -515
- package/skills/python-monorepo/references/namespace-packages.md +0 -426
- package/skills/python-monorepo/references/workspace-config.md +0 -341
- package/skills/python-sqlalchemy/SKILL.md +0 -119
- package/skills/python-sqlalchemy/references/async.md +0 -51
- package/skills/python-sqlalchemy/references/models.md +0 -67
- package/skills/python-sqlalchemy/references/queries.md +0 -57
- package/skills/python-temporal/SKILL.md +0 -128
- package/skills/python-temporal/references/patterns.md +0 -87
- package/skills/python-temporal/references/testing.md +0 -54
- package/skills/python-testing/SKILL.md +0 -161
- package/skills/python-testing/references/boundaries.md +0 -449
- package/skills/python-testing/references/mocking.md +0 -449
- package/skills/python-testing/references/pytest.md +0 -524
- package/skills/typescript-api-design/SKILL.md +0 -396
- package/skills/typescript-api-design/references/error-responses.md +0 -469
- package/skills/typescript-build-tools/SKILL.md +0 -447
- package/skills/typescript-build-tools/references/biome.md +0 -436
- package/skills/typescript-build-tools/references/bun.md +0 -432
- package/skills/typescript-build-tools/references/turborepo.md +0 -426
- package/skills/typescript-build-tools/references/vitest.md +0 -518
- package/skills/typescript-drizzle-orm/SKILL.md +0 -398
- package/skills/typescript-drizzle-orm/references/cloudflare.md +0 -392
- package/skills/typescript-drizzle-orm/references/entity-pattern.md +0 -412
- package/skills/typescript-drizzle-orm/references/postgresql.md +0 -191
- package/skills/typescript-drizzle-orm/references/repository-pattern.md +0 -479
- package/skills/typescript-drizzle-orm/references/sqlite.md +0 -291
- package/skills/typescript-dynamodb-toolbox/SKILL.md +0 -370
- package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +0 -574
- package/skills/typescript-dynamodb-toolbox/references/error-handling.md +0 -495
- package/skills/typescript-dynamodb-toolbox/references/modeling.md +0 -332
- package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +0 -631
- package/skills/typescript-dynamodb-toolbox/references/testing.md +0 -718
- package/skills/typescript-dynamodb-toolbox/references/transactions.md +0 -423
- package/skills/typescript-effect-ts/SKILL.md +0 -341
- package/skills/typescript-effect-ts/references/core.md +0 -412
- package/skills/typescript-effect-ts/references/error-handling.md +0 -468
- package/skills/typescript-effect-ts/references/resources.md +0 -476
- package/skills/typescript-effect-ts/references/schema.md +0 -536
- package/skills/typescript-effect-ts/references/services.md +0 -461
- package/skills/typescript-fastify/SKILL.md +0 -405
- package/skills/typescript-fastify/references/plugins.md +0 -329
- package/skills/typescript-fastify/references/typeid.md +0 -315
- package/skills/typescript-functional-patterns/SKILL.md +0 -402
- package/skills/typescript-functional-patterns/references/adts.md +0 -497
- package/skills/typescript-functional-patterns/references/branded-types.md +0 -542
- package/skills/typescript-functional-patterns/references/migration-guide.md +0 -695
- package/skills/typescript-functional-patterns/references/option-result.md +0 -585
- package/skills/typescript-testing/SKILL.md +0 -353
- package/skills/typescript-testing/references/mocking.md +0 -675
- package/skills/typescript-testing/references/msw.md +0 -852
- 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
|
-
```
|