@pageai/ralph-loop 1.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/.agent/PROMPT.md +58 -0
- package/.agent/STEERING.md +3 -0
- package/.agent/logs/LOG.md +13 -0
- package/.agent/prd/.gitkeep +0 -0
- package/.agent/screenshots/.gitkeep +0 -0
- package/.agent/skills/component-refactoring/SKILL.md +247 -0
- package/.agent/skills/component-refactoring/references/complexity-patterns.md +485 -0
- package/.agent/skills/component-refactoring/references/component-splitting.md +419 -0
- package/.agent/skills/component-refactoring/references/hook-extraction.md +317 -0
- package/.agent/skills/e2e-tester/SKILL.md +595 -0
- package/.agent/skills/frontend-code-review/SKILL.md +73 -0
- package/.agent/skills/frontend-code-review/references/code-quality.md +28 -0
- package/.agent/skills/frontend-code-review/references/performance.md +36 -0
- package/.agent/skills/frontend-testing/SKILL.md +316 -0
- package/.agent/skills/frontend-testing/assets/component-test.template.tsx +293 -0
- package/.agent/skills/frontend-testing/assets/hook-test.template.ts +207 -0
- package/.agent/skills/frontend-testing/assets/utility-test.template.ts +154 -0
- package/.agent/skills/frontend-testing/references/async-testing.md +345 -0
- package/.agent/skills/frontend-testing/references/checklist.md +188 -0
- package/.agent/skills/frontend-testing/references/common-patterns.md +449 -0
- package/.agent/skills/frontend-testing/references/mocking.md +289 -0
- package/.agent/skills/frontend-testing/references/workflow.md +265 -0
- package/.agent/skills/prd-creator/JSON.md +613 -0
- package/.agent/skills/prd-creator/PRD.md +196 -0
- package/.agent/skills/prd-creator/SKILL.md +143 -0
- package/.agent/skills/skill-creator/SKILL.md +355 -0
- package/.agent/skills/skill-creator/references/output-patterns.md +86 -0
- package/.agent/skills/skill-creator/references/workflows.md +28 -0
- package/.agent/skills/skill-creator/scripts/init_skill.py +300 -0
- package/.agent/skills/skill-creator/scripts/package_skill.py +110 -0
- package/.agent/skills/vercel-react-best-practices/AGENTS.md +2249 -0
- package/.agent/skills/vercel-react-best-practices/SKILL.md +125 -0
- package/.agent/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/.agent/skills/vercel-react-best-practices/rules/advanced-use-latest.md +49 -0
- package/.agent/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/.agent/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
- package/.agent/skills/vercel-react-best-practices/rules/async-dependencies.md +36 -0
- package/.agent/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/.agent/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/.agent/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/.agent/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/.agent/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/.agent/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/.agent/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/.agent/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/.agent/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/.agent/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/.agent/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/.agent/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/.agent/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/.agent/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/.agent/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/.agent/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/.agent/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/.agent/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/.agent/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/.agent/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/.agent/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/.agent/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/.agent/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/.agent/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/.agent/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/.agent/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/.agent/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/.agent/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/.agent/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/.agent/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/.agent/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/.agent/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/.agent/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/.agent/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/.agent/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/.agent/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/.agent/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/.agent/skills/vercel-react-best-practices/rules/server-cache-react.md +26 -0
- package/.agent/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +79 -0
- package/.agent/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/.agent/skills/vitest-best-practices/AGENTS.md +84 -0
- package/.agent/skills/vitest-best-practices/SKILL.md +130 -0
- package/.agent/skills/vitest-best-practices/references/aaa-pattern.md +260 -0
- package/.agent/skills/vitest-best-practices/references/assertions.md +393 -0
- package/.agent/skills/vitest-best-practices/references/async-testing.md +454 -0
- package/.agent/skills/vitest-best-practices/references/error-handling.md +382 -0
- package/.agent/skills/vitest-best-practices/references/organization.md +212 -0
- package/.agent/skills/vitest-best-practices/references/parameterized-tests.md +297 -0
- package/.agent/skills/vitest-best-practices/references/performance.md +528 -0
- package/.agent/skills/vitest-best-practices/references/snapshot-testing.md +483 -0
- package/.agent/skills/vitest-best-practices/references/test-doubles.md +499 -0
- package/.agent/skills/vitest-best-practices/references/vitest-features.md +529 -0
- package/.agent/skills/web-design-guidelines/SKILL.md +39 -0
- package/.agent/tasks/.gitkeep +0 -0
- package/.agent/tasks.json +1 -0
- package/.claude/agents/code-reviewer.md +172 -0
- package/.claude/commands/aw.md +50 -0
- package/.claude/hooks/play-sound.js +87 -0
- package/.claude/hooks/pre-tool-use.js +40 -0
- package/.claude/settings.json +54 -0
- package/.claude/settings.local.json +13 -0
- package/.mcp.json +31 -0
- package/AGENTS.md +44 -0
- package/CLAUDE.md +1 -0
- package/README.md +236 -0
- package/bin/cli.js +156 -0
- package/bin/lib/copy.js +149 -0
- package/bin/lib/display.js +137 -0
- package/package.json +65 -0
- package/ralph.sh +333 -0
- package/scripts/lib/args.sh +44 -0
- package/scripts/lib/cleanup.sh +53 -0
- package/scripts/lib/constants.sh +25 -0
- package/scripts/lib/display.sh +196 -0
- package/scripts/lib/logging.sh +30 -0
- package/scripts/lib/notify.sh +41 -0
- package/scripts/lib/output.sh +147 -0
- package/scripts/lib/preflight.sh +57 -0
- package/scripts/lib/preview.sh +77 -0
- package/scripts/lib/promise.sh +76 -0
- package/scripts/lib/spinner.sh +85 -0
- package/scripts/lib/terminal.sh +57 -0
- package/scripts/lib/timing.sh +223 -0
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
# 1.10 Snapshot Testing
|
|
2
|
+
|
|
3
|
+
Snapshots capture the output of code and save it for comparison in future test runs. Use them sparingly for appropriate use cases.
|
|
4
|
+
|
|
5
|
+
## When to Use Snapshots
|
|
6
|
+
|
|
7
|
+
**✅ Good use cases:**
|
|
8
|
+
- Testing complex object structures that rarely change
|
|
9
|
+
- Testing error messages and stack traces
|
|
10
|
+
- Testing serialized output (JSON, XML, HTML)
|
|
11
|
+
- Testing CLI output or formatted text
|
|
12
|
+
- Testing generated code or configurations
|
|
13
|
+
|
|
14
|
+
**❌ Bad use cases:**
|
|
15
|
+
- Testing dynamic data (dates, IDs, random values)
|
|
16
|
+
- Testing simple values (use explicit assertions instead)
|
|
17
|
+
- Testing implementation details
|
|
18
|
+
- Replacing proper assertions for laziness
|
|
19
|
+
|
|
20
|
+
## Basic Snapshot Testing
|
|
21
|
+
|
|
22
|
+
**✅ Correct: snapshot for complex structure**
|
|
23
|
+
```ts
|
|
24
|
+
import { describe, it, expect } from 'vitest';
|
|
25
|
+
import { generateConfig } from './config-generator';
|
|
26
|
+
|
|
27
|
+
describe('generateConfig', () => {
|
|
28
|
+
it('should generate correct config structure', () => {
|
|
29
|
+
const config = generateConfig({
|
|
30
|
+
environment: 'production',
|
|
31
|
+
features: ['auth', 'analytics'],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
expect(config).toMatchSnapshot();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**First run creates snapshot:**
|
|
40
|
+
```ts
|
|
41
|
+
// __snapshots__/config-generator.test.ts.snap
|
|
42
|
+
exports[`generateConfig > should generate correct config structure 1`] = `
|
|
43
|
+
{
|
|
44
|
+
"analytics": {
|
|
45
|
+
"enabled": true,
|
|
46
|
+
"provider": "google-analytics",
|
|
47
|
+
},
|
|
48
|
+
"auth": {
|
|
49
|
+
"enabled": true,
|
|
50
|
+
"provider": "oauth2",
|
|
51
|
+
"timeout": 3600,
|
|
52
|
+
},
|
|
53
|
+
"environment": "production",
|
|
54
|
+
}
|
|
55
|
+
`;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Inline Snapshots
|
|
59
|
+
|
|
60
|
+
For smaller snapshots, use inline snapshots to keep tests self-contained.
|
|
61
|
+
|
|
62
|
+
**✅ Correct: inline snapshot**
|
|
63
|
+
```ts
|
|
64
|
+
it('should format error message', () => {
|
|
65
|
+
const error = formatError({
|
|
66
|
+
code: 'AUTH_FAILED',
|
|
67
|
+
message: 'Invalid credentials',
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
expect(error).toMatchInlineSnapshot(`
|
|
71
|
+
{
|
|
72
|
+
"code": "AUTH_FAILED",
|
|
73
|
+
"message": "Invalid credentials",
|
|
74
|
+
"timestamp": Any<Date>,
|
|
75
|
+
}
|
|
76
|
+
`);
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Property Matchers
|
|
81
|
+
|
|
82
|
+
Use property matchers for dynamic values like dates and IDs.
|
|
83
|
+
|
|
84
|
+
**✅ Correct: snapshot with property matchers**
|
|
85
|
+
```ts
|
|
86
|
+
it('should create user with generated fields', () => {
|
|
87
|
+
const user = createUser({
|
|
88
|
+
name: 'John Doe',
|
|
89
|
+
email: 'john@example.com',
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(user).toMatchSnapshot({
|
|
93
|
+
id: expect.any(String),
|
|
94
|
+
createdAt: expect.any(Date),
|
|
95
|
+
updatedAt: expect.any(Date),
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Snapshot saved:**
|
|
101
|
+
```ts
|
|
102
|
+
exports[`should create user with generated fields 1`] = `
|
|
103
|
+
{
|
|
104
|
+
"createdAt": Any<Date>,
|
|
105
|
+
"email": "john@example.com",
|
|
106
|
+
"id": Any<String>,
|
|
107
|
+
"name": "John Doe",
|
|
108
|
+
"updatedAt": Any<Date>,
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Updating Snapshots
|
|
114
|
+
|
|
115
|
+
Update snapshots when intentional changes occur.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Update all snapshots
|
|
119
|
+
vitest run -u
|
|
120
|
+
|
|
121
|
+
# Update snapshots for specific file
|
|
122
|
+
vitest run user-service.test.ts -u
|
|
123
|
+
|
|
124
|
+
# Interactive update mode
|
|
125
|
+
vitest --ui
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**⚠️ Warning: Review changes carefully**
|
|
129
|
+
```bash
|
|
130
|
+
# Before updating, review what changed
|
|
131
|
+
vitest run
|
|
132
|
+
|
|
133
|
+
# Check git diff to see snapshot changes
|
|
134
|
+
git diff __snapshots__/
|
|
135
|
+
|
|
136
|
+
# Only update if changes are intentional
|
|
137
|
+
vitest run -u
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Testing React Components
|
|
141
|
+
|
|
142
|
+
Snapshots work well for component output (but prefer visual regression tools for styling).
|
|
143
|
+
|
|
144
|
+
**✅ Correct: component snapshot**
|
|
145
|
+
```ts
|
|
146
|
+
import { render } from '@testing-library/react';
|
|
147
|
+
|
|
148
|
+
it('should render user profile', () => {
|
|
149
|
+
const { container } = render(
|
|
150
|
+
<UserProfile
|
|
151
|
+
name="John Doe"
|
|
152
|
+
email="john@example.com"
|
|
153
|
+
role="admin"
|
|
154
|
+
/>
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
expect(container.firstChild).toMatchSnapshot();
|
|
158
|
+
});
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**✅ Better: snapshot with property testing**
|
|
162
|
+
```ts
|
|
163
|
+
it('should render user profile with all fields', () => {
|
|
164
|
+
const { getByText, getByRole } = render(
|
|
165
|
+
<UserProfile
|
|
166
|
+
name="John Doe"
|
|
167
|
+
email="john@example.com"
|
|
168
|
+
role="admin"
|
|
169
|
+
/>
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
// Test specific behaviors
|
|
173
|
+
expect(getByText('John Doe')).toBeInTheDocument();
|
|
174
|
+
expect(getByText('john@example.com')).toBeInTheDocument();
|
|
175
|
+
expect(getByRole('img')).toHaveAttribute('alt', 'John Doe');
|
|
176
|
+
|
|
177
|
+
// Snapshot for full output
|
|
178
|
+
expect(container.firstChild).toMatchSnapshot();
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Testing Error Messages
|
|
183
|
+
|
|
184
|
+
Snapshots work well for error messages that include context.
|
|
185
|
+
|
|
186
|
+
**✅ Correct: error snapshot**
|
|
187
|
+
```ts
|
|
188
|
+
it('should throw detailed validation error', () => {
|
|
189
|
+
const invalidData = {
|
|
190
|
+
email: 'invalid-email',
|
|
191
|
+
age: -5,
|
|
192
|
+
name: '',
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
expect(() => validateUser(invalidData)).toThrowErrorMatchingSnapshot();
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Snapshot:**
|
|
200
|
+
```ts
|
|
201
|
+
exports[`should throw detailed validation error 1`] = `
|
|
202
|
+
[ValidationError: User validation failed:
|
|
203
|
+
- email: Invalid email format
|
|
204
|
+
- age: Age must be between 0 and 150
|
|
205
|
+
- name: Name is required]
|
|
206
|
+
`;
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Serializers
|
|
210
|
+
|
|
211
|
+
Custom serializers normalize output for consistent snapshots.
|
|
212
|
+
|
|
213
|
+
**✅ Correct: custom serializer for dates**
|
|
214
|
+
```ts
|
|
215
|
+
import { expect } from 'vitest';
|
|
216
|
+
|
|
217
|
+
expect.addSnapshotSerializer({
|
|
218
|
+
test: (val) => val instanceof Date,
|
|
219
|
+
serialize: (val) => `Date<${val.toISOString()}>`,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should create order with timestamp', () => {
|
|
223
|
+
const order = createOrder({ items: [] });
|
|
224
|
+
|
|
225
|
+
expect(order).toMatchInlineSnapshot(`
|
|
226
|
+
{
|
|
227
|
+
"createdAt": Date<2024-01-15T10:30:00.000Z>,
|
|
228
|
+
"id": "order-123",
|
|
229
|
+
"items": [],
|
|
230
|
+
}
|
|
231
|
+
`);
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## When NOT to Use Snapshots
|
|
236
|
+
|
|
237
|
+
**❌ Incorrect: snapshot for simple value**
|
|
238
|
+
```ts
|
|
239
|
+
it('should return user name', () => {
|
|
240
|
+
const name = getUserName(user);
|
|
241
|
+
expect(name).toMatchInlineSnapshot(`"John Doe"`);
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
**✅ Correct: explicit assertion**
|
|
246
|
+
```ts
|
|
247
|
+
it('should return user name', () => {
|
|
248
|
+
const name = getUserName(user);
|
|
249
|
+
expect(name).toBe('John Doe');
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**❌ Incorrect: snapshot with dynamic data**
|
|
254
|
+
```ts
|
|
255
|
+
it('should generate report', () => {
|
|
256
|
+
const report = generateReport();
|
|
257
|
+
|
|
258
|
+
// Snapshot will change every run!
|
|
259
|
+
expect(report).toMatchSnapshot();
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**✅ Correct: snapshot with matchers**
|
|
264
|
+
```ts
|
|
265
|
+
it('should generate report', () => {
|
|
266
|
+
const report = generateReport();
|
|
267
|
+
|
|
268
|
+
expect(report).toMatchSnapshot({
|
|
269
|
+
generatedAt: expect.any(Date),
|
|
270
|
+
id: expect.any(String),
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Testing CLI Output
|
|
276
|
+
|
|
277
|
+
Snapshots work well for command-line output.
|
|
278
|
+
|
|
279
|
+
**✅ Correct: CLI output snapshot**
|
|
280
|
+
```ts
|
|
281
|
+
it('should display help text', () => {
|
|
282
|
+
const output = cli.getHelpText();
|
|
283
|
+
|
|
284
|
+
expect(output).toMatchInlineSnapshot(`
|
|
285
|
+
"Usage: myapp [options] [command]
|
|
286
|
+
|
|
287
|
+
Options:
|
|
288
|
+
-v, --version Output the version number
|
|
289
|
+
-h, --help Display help for command
|
|
290
|
+
|
|
291
|
+
Commands:
|
|
292
|
+
start [options] Start the application
|
|
293
|
+
stop Stop the application
|
|
294
|
+
status Show application status"
|
|
295
|
+
`);
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Testing JSON/API Responses
|
|
300
|
+
|
|
301
|
+
**✅ Correct: API response snapshot**
|
|
302
|
+
```ts
|
|
303
|
+
it('should return user API response', async () => {
|
|
304
|
+
const response = await api.getUser('user-123');
|
|
305
|
+
|
|
306
|
+
expect(response).toMatchSnapshot({
|
|
307
|
+
data: {
|
|
308
|
+
id: expect.any(String),
|
|
309
|
+
createdAt: expect.any(String),
|
|
310
|
+
updatedAt: expect.any(String),
|
|
311
|
+
},
|
|
312
|
+
meta: {
|
|
313
|
+
requestId: expect.any(String),
|
|
314
|
+
timestamp: expect.any(Number),
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Snapshot Best Practices
|
|
321
|
+
|
|
322
|
+
### 1. Keep Snapshots Small
|
|
323
|
+
|
|
324
|
+
**❌ Incorrect: massive snapshot**
|
|
325
|
+
```ts
|
|
326
|
+
it('should render entire page', () => {
|
|
327
|
+
const { container } = render(<App />);
|
|
328
|
+
expect(container).toMatchSnapshot(); // Huge snapshot!
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**✅ Correct: focused snapshot**
|
|
333
|
+
```ts
|
|
334
|
+
it('should render header', () => {
|
|
335
|
+
const { container } = render(<Header user={mockUser} />);
|
|
336
|
+
expect(container).toMatchSnapshot();
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it('should render navigation', () => {
|
|
340
|
+
const { container } = render(<Navigation items={mockItems} />);
|
|
341
|
+
expect(container).toMatchSnapshot();
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### 2. Name Snapshots Clearly
|
|
346
|
+
|
|
347
|
+
**❌ Incorrect: generic name**
|
|
348
|
+
```ts
|
|
349
|
+
it('test 1', () => {
|
|
350
|
+
expect(result).toMatchSnapshot();
|
|
351
|
+
});
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
**✅ Correct: descriptive name**
|
|
355
|
+
```ts
|
|
356
|
+
it('should format currency with symbol and decimals', () => {
|
|
357
|
+
expect(formatCurrency(1234.56, 'USD')).toMatchInlineSnapshot(`"$1,234.56"`);
|
|
358
|
+
});
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### 3. Review Snapshot Changes
|
|
362
|
+
|
|
363
|
+
Always review snapshot updates in code review.
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
# In CI/CD, ensure snapshots match
|
|
367
|
+
vitest run
|
|
368
|
+
|
|
369
|
+
# Fail if snapshots need updating
|
|
370
|
+
# This prevents accidental updates
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**✅ Correct: intentional update**
|
|
374
|
+
```
|
|
375
|
+
1. Make code change
|
|
376
|
+
2. Run tests - see snapshot diff
|
|
377
|
+
3. Review diff carefully
|
|
378
|
+
4. Update if intentional: vitest run -u
|
|
379
|
+
5. Commit updated snapshots
|
|
380
|
+
6. Explain changes in PR description
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### 4. Combine with Explicit Assertions
|
|
384
|
+
|
|
385
|
+
**✅ Correct: snapshots + assertions**
|
|
386
|
+
```ts
|
|
387
|
+
it('should generate invoice', () => {
|
|
388
|
+
const invoice = generateInvoice({
|
|
389
|
+
items: [{ name: 'Widget', price: 10, quantity: 2 }],
|
|
390
|
+
tax: 0.08,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Explicit assertions for critical values
|
|
394
|
+
expect(invoice.subtotal).toBe(20);
|
|
395
|
+
expect(invoice.tax).toBe(1.6);
|
|
396
|
+
expect(invoice.total).toBe(21.6);
|
|
397
|
+
|
|
398
|
+
// Snapshot for full structure
|
|
399
|
+
expect(invoice).toMatchSnapshot({
|
|
400
|
+
id: expect.any(String),
|
|
401
|
+
createdAt: expect.any(Date),
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## Snapshot Anti-Patterns
|
|
407
|
+
|
|
408
|
+
**❌ Incorrect: using snapshots as a crutch**
|
|
409
|
+
```ts
|
|
410
|
+
it('should work', () => {
|
|
411
|
+
// Lazy: just snapshot everything instead of thinking about what to test
|
|
412
|
+
expect(doSomething()).toMatchSnapshot();
|
|
413
|
+
});
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**❌ Incorrect: ignoring failing snapshots**
|
|
417
|
+
```bash
|
|
418
|
+
# Don't blindly update snapshots without understanding why they changed
|
|
419
|
+
vitest run -u # ❌ Without reviewing changes first
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**❌ Incorrect: snapshots for test doubles**
|
|
423
|
+
```ts
|
|
424
|
+
it('should call service', () => {
|
|
425
|
+
const mockService = vi.fn();
|
|
426
|
+
callService(mockService);
|
|
427
|
+
|
|
428
|
+
// Don't snapshot mock calls!
|
|
429
|
+
expect(mockService.mock.calls).toMatchSnapshot();
|
|
430
|
+
});
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**✅ Correct: explicit assertions for mocks**
|
|
434
|
+
```ts
|
|
435
|
+
it('should call service with correct params', () => {
|
|
436
|
+
const mockService = vi.fn();
|
|
437
|
+
callService(mockService);
|
|
438
|
+
|
|
439
|
+
expect(mockService).toHaveBeenCalledWith({
|
|
440
|
+
id: '123',
|
|
441
|
+
action: 'update',
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Organizing Snapshots
|
|
447
|
+
|
|
448
|
+
Snapshots are stored in `__snapshots__/` directories.
|
|
449
|
+
|
|
450
|
+
```
|
|
451
|
+
src/
|
|
452
|
+
components/
|
|
453
|
+
button.tsx
|
|
454
|
+
button.test.tsx
|
|
455
|
+
__snapshots__/
|
|
456
|
+
button.test.tsx.snap
|
|
457
|
+
services/
|
|
458
|
+
user-service.ts
|
|
459
|
+
user-service.test.ts
|
|
460
|
+
__snapshots__/
|
|
461
|
+
user-service.test.ts.snap
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**✅ Correct: commit snapshots**
|
|
465
|
+
```bash
|
|
466
|
+
# Always commit snapshot files
|
|
467
|
+
git add src/**/__snapshots__
|
|
468
|
+
git commit -m "Update snapshots after button styling changes"
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
## When to Prefer Explicit Assertions
|
|
472
|
+
|
|
473
|
+
Use explicit assertions when:
|
|
474
|
+
- Testing simple values
|
|
475
|
+
- Testing critical business logic
|
|
476
|
+
- Testing behavior, not structure
|
|
477
|
+
- You need clear failure messages
|
|
478
|
+
|
|
479
|
+
Use snapshots when:
|
|
480
|
+
- Testing complex structures
|
|
481
|
+
- Output format matters
|
|
482
|
+
- Regression testing large outputs
|
|
483
|
+
- Maintaining consistency across many similar tests
|