@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.
Files changed (120) hide show
  1. package/.agent/PROMPT.md +58 -0
  2. package/.agent/STEERING.md +3 -0
  3. package/.agent/logs/LOG.md +13 -0
  4. package/.agent/prd/.gitkeep +0 -0
  5. package/.agent/screenshots/.gitkeep +0 -0
  6. package/.agent/skills/component-refactoring/SKILL.md +247 -0
  7. package/.agent/skills/component-refactoring/references/complexity-patterns.md +485 -0
  8. package/.agent/skills/component-refactoring/references/component-splitting.md +419 -0
  9. package/.agent/skills/component-refactoring/references/hook-extraction.md +317 -0
  10. package/.agent/skills/e2e-tester/SKILL.md +595 -0
  11. package/.agent/skills/frontend-code-review/SKILL.md +73 -0
  12. package/.agent/skills/frontend-code-review/references/code-quality.md +28 -0
  13. package/.agent/skills/frontend-code-review/references/performance.md +36 -0
  14. package/.agent/skills/frontend-testing/SKILL.md +316 -0
  15. package/.agent/skills/frontend-testing/assets/component-test.template.tsx +293 -0
  16. package/.agent/skills/frontend-testing/assets/hook-test.template.ts +207 -0
  17. package/.agent/skills/frontend-testing/assets/utility-test.template.ts +154 -0
  18. package/.agent/skills/frontend-testing/references/async-testing.md +345 -0
  19. package/.agent/skills/frontend-testing/references/checklist.md +188 -0
  20. package/.agent/skills/frontend-testing/references/common-patterns.md +449 -0
  21. package/.agent/skills/frontend-testing/references/mocking.md +289 -0
  22. package/.agent/skills/frontend-testing/references/workflow.md +265 -0
  23. package/.agent/skills/prd-creator/JSON.md +613 -0
  24. package/.agent/skills/prd-creator/PRD.md +196 -0
  25. package/.agent/skills/prd-creator/SKILL.md +143 -0
  26. package/.agent/skills/skill-creator/SKILL.md +355 -0
  27. package/.agent/skills/skill-creator/references/output-patterns.md +86 -0
  28. package/.agent/skills/skill-creator/references/workflows.md +28 -0
  29. package/.agent/skills/skill-creator/scripts/init_skill.py +300 -0
  30. package/.agent/skills/skill-creator/scripts/package_skill.py +110 -0
  31. package/.agent/skills/vercel-react-best-practices/AGENTS.md +2249 -0
  32. package/.agent/skills/vercel-react-best-practices/SKILL.md +125 -0
  33. package/.agent/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  34. package/.agent/skills/vercel-react-best-practices/rules/advanced-use-latest.md +49 -0
  35. package/.agent/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
  36. package/.agent/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
  37. package/.agent/skills/vercel-react-best-practices/rules/async-dependencies.md +36 -0
  38. package/.agent/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
  39. package/.agent/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
  40. package/.agent/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
  41. package/.agent/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
  42. package/.agent/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
  43. package/.agent/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  44. package/.agent/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
  45. package/.agent/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
  46. package/.agent/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
  47. package/.agent/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +82 -0
  48. package/.agent/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
  49. package/.agent/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
  50. package/.agent/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
  51. package/.agent/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
  52. package/.agent/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
  53. package/.agent/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
  54. package/.agent/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
  55. package/.agent/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
  56. package/.agent/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
  57. package/.agent/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
  58. package/.agent/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
  59. package/.agent/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
  60. package/.agent/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  61. package/.agent/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
  62. package/.agent/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
  63. package/.agent/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  64. package/.agent/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  65. package/.agent/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
  66. package/.agent/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
  67. package/.agent/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
  68. package/.agent/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
  69. package/.agent/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
  70. package/.agent/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  71. package/.agent/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
  72. package/.agent/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
  73. package/.agent/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
  74. package/.agent/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
  75. package/.agent/skills/vercel-react-best-practices/rules/server-cache-react.md +26 -0
  76. package/.agent/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +79 -0
  77. package/.agent/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
  78. package/.agent/skills/vitest-best-practices/AGENTS.md +84 -0
  79. package/.agent/skills/vitest-best-practices/SKILL.md +130 -0
  80. package/.agent/skills/vitest-best-practices/references/aaa-pattern.md +260 -0
  81. package/.agent/skills/vitest-best-practices/references/assertions.md +393 -0
  82. package/.agent/skills/vitest-best-practices/references/async-testing.md +454 -0
  83. package/.agent/skills/vitest-best-practices/references/error-handling.md +382 -0
  84. package/.agent/skills/vitest-best-practices/references/organization.md +212 -0
  85. package/.agent/skills/vitest-best-practices/references/parameterized-tests.md +297 -0
  86. package/.agent/skills/vitest-best-practices/references/performance.md +528 -0
  87. package/.agent/skills/vitest-best-practices/references/snapshot-testing.md +483 -0
  88. package/.agent/skills/vitest-best-practices/references/test-doubles.md +499 -0
  89. package/.agent/skills/vitest-best-practices/references/vitest-features.md +529 -0
  90. package/.agent/skills/web-design-guidelines/SKILL.md +39 -0
  91. package/.agent/tasks/.gitkeep +0 -0
  92. package/.agent/tasks.json +1 -0
  93. package/.claude/agents/code-reviewer.md +172 -0
  94. package/.claude/commands/aw.md +50 -0
  95. package/.claude/hooks/play-sound.js +87 -0
  96. package/.claude/hooks/pre-tool-use.js +40 -0
  97. package/.claude/settings.json +54 -0
  98. package/.claude/settings.local.json +13 -0
  99. package/.mcp.json +31 -0
  100. package/AGENTS.md +44 -0
  101. package/CLAUDE.md +1 -0
  102. package/README.md +236 -0
  103. package/bin/cli.js +156 -0
  104. package/bin/lib/copy.js +149 -0
  105. package/bin/lib/display.js +137 -0
  106. package/package.json +65 -0
  107. package/ralph.sh +333 -0
  108. package/scripts/lib/args.sh +44 -0
  109. package/scripts/lib/cleanup.sh +53 -0
  110. package/scripts/lib/constants.sh +25 -0
  111. package/scripts/lib/display.sh +196 -0
  112. package/scripts/lib/logging.sh +30 -0
  113. package/scripts/lib/notify.sh +41 -0
  114. package/scripts/lib/output.sh +147 -0
  115. package/scripts/lib/preflight.sh +57 -0
  116. package/scripts/lib/preview.sh +77 -0
  117. package/scripts/lib/promise.sh +76 -0
  118. package/scripts/lib/spinner.sh +85 -0
  119. package/scripts/lib/terminal.sh +57 -0
  120. package/scripts/lib/timing.sh +223 -0
@@ -0,0 +1,499 @@
1
+ # 1.6 Test Doubles
2
+
3
+ Test doubles replace real dependencies in tests. Use them sparingly and prefer real implementations when practical.
4
+
5
+ ## Hierarchy of Test Doubles
6
+
7
+ Prefer in this order (best to worst):
8
+
9
+ 1. **Real Implementation**: Use the actual code whenever possible
10
+ 2. **Fakes**: Lightweight working implementations (e.g., in-memory database)
11
+ 3. **Stubs**: Return pre-configured responses without behavior
12
+ 4. **Spies**: Record calls while allowing real implementation to run
13
+ 5. **Mocks**: Replace behavior AND verify interactions (last resort)
14
+
15
+ ## When to Use Test Doubles
16
+
17
+ **✅ DO mock these:**
18
+ - External services (APIs, databases, file systems)
19
+ - Third-party libraries you don't control
20
+ - Non-deterministic functions (Date.now(), Math.random())
21
+ - Slow operations (network calls, large file I/O)
22
+
23
+ **❌ DON'T mock these:**
24
+ - Pure functions (deterministic, no side effects)
25
+ - Your own application code
26
+ - Simple utilities (array helpers, formatters)
27
+ - Code you're actively testing
28
+
29
+ ## 1. Real Implementation (Preferred)
30
+
31
+ Use real code whenever possible - it provides the most confidence.
32
+
33
+ **✅ Correct: using real implementation**
34
+ ```ts
35
+ describe('OrderService', () => {
36
+ it('should calculate order total correctly', () => {
37
+ const priceCalculator = new PriceCalculator(); // Real implementation
38
+ const orderService = new OrderService(priceCalculator);
39
+
40
+ const order = orderService.createOrder([
41
+ { id: 1, price: 10, quantity: 2 },
42
+ { id: 2, price: 5, quantity: 3 },
43
+ ]);
44
+
45
+ expect(order.total).toEqual(35);
46
+ });
47
+ });
48
+ ```
49
+
50
+ ## 2. Fakes
51
+
52
+ Fakes are lightweight implementations with real behavior.
53
+
54
+ **✅ Correct: in-memory fake for database**
55
+ ```ts
56
+ class FakeUserRepository implements UserRepository {
57
+ private users: Map<string, User> = new Map();
58
+
59
+ async save(user: User): Promise<void> {
60
+ this.users.set(user.id, user);
61
+ }
62
+
63
+ async findById(id: string): Promise<User | null> {
64
+ return this.users.get(id) || null;
65
+ }
66
+
67
+ async findAll(): Promise<User[]> {
68
+ return Array.from(this.users.values());
69
+ }
70
+
71
+ clear() {
72
+ this.users.clear();
73
+ }
74
+ }
75
+
76
+ describe('UserService', () => {
77
+ let userRepo: FakeUserRepository;
78
+ let userService: UserService;
79
+
80
+ beforeEach(() => {
81
+ userRepo = new FakeUserRepository();
82
+ userService = new UserService(userRepo);
83
+ });
84
+
85
+ it('should create and retrieve user', async () => {
86
+ const user = await userService.createUser({
87
+ name: 'John',
88
+ email: 'john@example.com',
89
+ });
90
+
91
+ const retrieved = await userService.getUser(user.id);
92
+
93
+ expect(retrieved).toEqual(user);
94
+ });
95
+
96
+ it('should list all users', async () => {
97
+ await userService.createUser({ name: 'Alice', email: 'alice@example.com' });
98
+ await userService.createUser({ name: 'Bob', email: 'bob@example.com' });
99
+
100
+ const users = await userService.getAllUsers();
101
+
102
+ expect(users).toHaveLength(2);
103
+ });
104
+ });
105
+ ```
106
+
107
+ **✅ Correct: fake for external API**
108
+ ```ts
109
+ class FakePaymentGateway implements PaymentGateway {
110
+ private payments: Payment[] = [];
111
+
112
+ async charge(amount: number, token: string): Promise<PaymentResult> {
113
+ const payment: Payment = {
114
+ id: `pay_${Date.now()}`,
115
+ amount,
116
+ token,
117
+ status: 'succeeded',
118
+ createdAt: new Date(),
119
+ };
120
+
121
+ this.payments.push(payment);
122
+
123
+ return { success: true, paymentId: payment.id };
124
+ }
125
+
126
+ getPayments(): Payment[] {
127
+ return [...this.payments];
128
+ }
129
+ }
130
+ ```
131
+
132
+ ## 3. Stubs
133
+
134
+ Stubs return pre-configured responses without implementing real behavior.
135
+
136
+ **✅ Correct: stubbing external API call**
137
+ ```ts
138
+ describe('WeatherService', () => {
139
+ it('should return temperature for city', async () => {
140
+ const apiClient = {
141
+ fetch: vi.fn().mockResolvedValue({
142
+ temperature: 72,
143
+ conditions: 'sunny',
144
+ city: 'San Francisco',
145
+ }),
146
+ };
147
+
148
+ const weatherService = new WeatherService(apiClient);
149
+ const weather = await weatherService.getWeather('San Francisco');
150
+
151
+ expect(weather.temperature).toEqual(72);
152
+ expect(apiClient.fetch).toHaveBeenCalledWith('/weather?city=San+Francisco');
153
+ });
154
+
155
+ it('should handle API errors', async () => {
156
+ const apiClient = {
157
+ fetch: vi.fn().mockRejectedValue(new Error('API unavailable')),
158
+ };
159
+
160
+ const weatherService = new WeatherService(apiClient);
161
+
162
+ await expect(weatherService.getWeather('Invalid'))
163
+ .rejects.toThrow('API unavailable');
164
+ });
165
+ });
166
+ ```
167
+
168
+ **✅ Correct: stubbing multiple scenarios**
169
+ ```ts
170
+ describe('DataService', () => {
171
+ it('should retry on failure then succeed', async () => {
172
+ const apiClient = {
173
+ fetch: vi.fn()
174
+ .mockRejectedValueOnce(new Error('Timeout'))
175
+ .mockRejectedValueOnce(new Error('Timeout'))
176
+ .mockResolvedValueOnce({ data: 'success' }),
177
+ };
178
+
179
+ const service = new DataService(apiClient);
180
+ const result = await service.fetchWithRetry('/api/data');
181
+
182
+ expect(result).toEqual({ data: 'success' });
183
+ expect(apiClient.fetch).toHaveBeenCalledTimes(3);
184
+ });
185
+ });
186
+ ```
187
+
188
+ ## 4. Spies
189
+
190
+ Spies record function calls while preserving original behavior.
191
+
192
+ **✅ Correct: spying on method calls**
193
+ ```ts
194
+ describe('Logger', () => {
195
+ it('should log errors to console', () => {
196
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
197
+
198
+ const logger = new Logger();
199
+ logger.error('Something went wrong');
200
+
201
+ expect(consoleSpy).toHaveBeenCalledWith('[ERROR]', 'Something went wrong');
202
+
203
+ consoleSpy.mockRestore();
204
+ });
205
+ });
206
+ ```
207
+
208
+ **✅ Correct: spying to verify side effects**
209
+ ```ts
210
+ describe('Analytics', () => {
211
+ it('should track page views', () => {
212
+ const trackingSpy = vi.fn();
213
+ const analytics = new Analytics({ track: trackingSpy });
214
+
215
+ analytics.pageView('/home', { userId: '123' });
216
+
217
+ expect(trackingSpy).toHaveBeenCalledWith('pageview', {
218
+ path: '/home',
219
+ userId: '123',
220
+ });
221
+ });
222
+ });
223
+ ```
224
+
225
+ ## 5. Mocks (Use Sparingly)
226
+
227
+ Mocks replace behavior AND verify interactions. Use only when necessary.
228
+
229
+ **✅ Correct: mocking external service**
230
+ ```ts
231
+ describe('EmailService', () => {
232
+ it('should send welcome email to new users', async () => {
233
+ const emailProvider = {
234
+ send: vi.fn().mockResolvedValue({ messageId: 'msg-123' }),
235
+ };
236
+
237
+ const emailService = new EmailService(emailProvider);
238
+ await emailService.sendWelcomeEmail('user@example.com');
239
+
240
+ expect(emailProvider.send).toHaveBeenCalledWith({
241
+ to: 'user@example.com',
242
+ subject: 'Welcome!',
243
+ body: expect.stringContaining('Welcome'),
244
+ });
245
+ });
246
+ });
247
+ ```
248
+
249
+ **❌ Incorrect: over-mocking internal code**
250
+ ```ts
251
+ describe('OrderProcessor', () => {
252
+ it('should process order', () => {
253
+ const calculateTaxMock = vi.fn().mockReturnValue(5);
254
+ const calculateShippingMock = vi.fn().mockReturnValue(10);
255
+ const formatPriceMock = vi.fn().mockReturnValue('$50.00');
256
+
257
+ // Too many mocks! Just use real implementations
258
+ const processor = new OrderProcessor({
259
+ calculateTax: calculateTaxMock,
260
+ calculateShipping: calculateShippingMock,
261
+ formatPrice: formatPriceMock,
262
+ });
263
+
264
+ // ...test logic
265
+ });
266
+ });
267
+ ```
268
+ *Why?* Mocking your own simple functions makes tests brittle and less valuable. Use real implementations.
269
+
270
+ ## vi.fn() - Manual Mocks
271
+
272
+ Create mock functions when you need fine control.
273
+
274
+ **✅ Correct: creating mock with specific behavior**
275
+ ```ts
276
+ describe('DataFetcher', () => {
277
+ it('should handle pagination', async () => {
278
+ const fetchFn = vi.fn()
279
+ .mockResolvedValueOnce({ items: [1, 2, 3], hasMore: true })
280
+ .mockResolvedValueOnce({ items: [4, 5, 6], hasMore: true })
281
+ .mockResolvedValueOnce({ items: [7, 8], hasMore: false });
282
+
283
+ const fetcher = new DataFetcher(fetchFn);
284
+ const allItems = await fetcher.fetchAll();
285
+
286
+ expect(allItems).toEqual([1, 2, 3, 4, 5, 6, 7, 8]);
287
+ expect(fetchFn).toHaveBeenCalledTimes(3);
288
+ });
289
+ });
290
+ ```
291
+
292
+ ## vi.mock() - Module Mocking
293
+
294
+ Mock entire modules when you need to replace external dependencies.
295
+
296
+ **✅ Correct: mocking external module**
297
+ ```ts
298
+ import { v4 as uuidv4 } from 'uuid';
299
+
300
+ vi.mock('uuid', () => ({
301
+ v4: vi.fn(),
302
+ }));
303
+
304
+ describe('UserService', () => {
305
+ it('should generate unique user IDs', () => {
306
+ vi.mocked(uuidv4)
307
+ .mockReturnValueOnce('id-1')
308
+ .mockReturnValueOnce('id-2');
309
+
310
+ const service = new UserService();
311
+ const user1 = service.createUser({ name: 'Alice' });
312
+ const user2 = service.createUser({ name: 'Bob' });
313
+
314
+ expect(user1.id).toBe('id-1');
315
+ expect(user2.id).toBe('id-2');
316
+ });
317
+ });
318
+ ```
319
+
320
+ **✅ Correct: partial module mock**
321
+ ```ts
322
+ vi.mock('../utils', async () => {
323
+ const actual = await vi.importActual('../utils');
324
+ return {
325
+ ...actual,
326
+ fetchData: vi.fn(), // Only mock fetchData, keep other utils real
327
+ };
328
+ });
329
+ ```
330
+
331
+ ## Mocking Timers
332
+
333
+ Use fake timers for testing time-dependent code.
334
+
335
+ **✅ Correct: testing debounce with fake timers**
336
+ ```ts
337
+ describe('debounce', () => {
338
+ beforeEach(() => {
339
+ vi.useFakeTimers();
340
+ });
341
+
342
+ afterEach(() => {
343
+ vi.useRealTimers();
344
+ });
345
+
346
+ it('should delay function execution', () => {
347
+ const callback = vi.fn();
348
+ const debounced = debounce(callback, 1000);
349
+
350
+ debounced();
351
+ debounced();
352
+ debounced();
353
+
354
+ expect(callback).not.toHaveBeenCalled();
355
+
356
+ vi.advanceTimersByTime(1000);
357
+
358
+ expect(callback).toHaveBeenCalledTimes(1);
359
+ });
360
+ });
361
+ ```
362
+
363
+ **✅ Correct: testing intervals**
364
+ ```ts
365
+ it('should poll every 5 seconds', () => {
366
+ vi.useFakeTimers();
367
+
368
+ const pollFn = vi.fn();
369
+ const poller = new Poller(pollFn, 5000);
370
+
371
+ poller.start();
372
+
373
+ expect(pollFn).toHaveBeenCalledTimes(1);
374
+
375
+ vi.advanceTimersByTime(5000);
376
+ expect(pollFn).toHaveBeenCalledTimes(2);
377
+
378
+ vi.advanceTimersByTime(5000);
379
+ expect(pollFn).toHaveBeenCalledTimes(3);
380
+
381
+ poller.stop();
382
+ vi.useRealTimers();
383
+ });
384
+ ```
385
+
386
+ ## Testing with Dates
387
+
388
+ Mock dates for consistent test results.
389
+
390
+ **✅ Correct: mocking current date**
391
+ ```ts
392
+ describe('isExpired', () => {
393
+ beforeEach(() => {
394
+ vi.useFakeTimers();
395
+ vi.setSystemTime(new Date('2024-01-01'));
396
+ });
397
+
398
+ afterEach(() => {
399
+ vi.useRealTimers();
400
+ });
401
+
402
+ it('should return true for expired items', () => {
403
+ const expiredItem = { expiryDate: new Date('2023-12-31') };
404
+ expect(isExpired(expiredItem)).toBe(true);
405
+ });
406
+
407
+ it('should return false for valid items', () => {
408
+ const validItem = { expiryDate: new Date('2024-12-31') };
409
+ expect(isExpired(validItem)).toBe(false);
410
+ });
411
+ });
412
+ ```
413
+
414
+ ## Clearing and Restoring Mocks
415
+
416
+ Always clean up mocks between tests.
417
+
418
+ **✅ Correct: clearing mocks**
419
+ ```ts
420
+ describe('UserService', () => {
421
+ const mockApi = {
422
+ fetchUser: vi.fn(),
423
+ };
424
+
425
+ beforeEach(() => {
426
+ mockApi.fetchUser.mockClear(); // Clear call history
427
+ });
428
+
429
+ it('test 1', async () => {
430
+ mockApi.fetchUser.mockResolvedValue({ id: '1', name: 'Alice' });
431
+ // ... test logic
432
+ });
433
+
434
+ it('test 2', async () => {
435
+ mockApi.fetchUser.mockResolvedValue({ id: '2', name: 'Bob' });
436
+ // ... test logic
437
+ });
438
+ });
439
+ ```
440
+
441
+ **✅ Correct: restoring spies**
442
+ ```ts
443
+ describe('Logger', () => {
444
+ afterEach(() => {
445
+ vi.restoreAllMocks(); // Restore all spies
446
+ });
447
+
448
+ it('should log to console', () => {
449
+ const spy = vi.spyOn(console, 'log');
450
+ // ... test logic
451
+ });
452
+ });
453
+ ```
454
+
455
+ ## Anti-Patterns
456
+
457
+ **❌ Incorrect: mocking everything**
458
+ ```ts
459
+ it('should create user', () => {
460
+ const mockValidate = vi.fn().mockReturnValue(true);
461
+ const mockHash = vi.fn().mockReturnValue('hashed');
462
+ const mockGenId = vi.fn().mockReturnValue('id-123');
463
+ const mockSave = vi.fn();
464
+
465
+ // Way too many mocks - just use real code!
466
+ const service = new UserService({
467
+ validate: mockValidate,
468
+ hash: mockHash,
469
+ generateId: mockGenId,
470
+ save: mockSave,
471
+ });
472
+ });
473
+ ```
474
+
475
+ **❌ Incorrect: testing mocks instead of behavior**
476
+ ```ts
477
+ it('should call formatUser', () => {
478
+ const mockFormat = vi.fn();
479
+ service.formatUser = mockFormat;
480
+
481
+ service.getUser('123');
482
+
483
+ expect(mockFormat).toHaveBeenCalled(); // Who cares if it was called?
484
+ });
485
+ ```
486
+ *Why?* Test the actual behavior (what the user gets), not internal implementation (which functions were called).
487
+
488
+ **❌ Incorrect: brittle interaction testing**
489
+ ```ts
490
+ it('should process user', () => {
491
+ service.processUser(user);
492
+
493
+ expect(logger.log).toHaveBeenCalledTimes(3); // Fragile!
494
+ expect(logger.log).toHaveBeenNthCalledWith(1, 'start');
495
+ expect(logger.log).toHaveBeenNthCalledWith(2, 'processing');
496
+ expect(logger.log).toHaveBeenNthCalledWith(3, 'done');
497
+ });
498
+ ```
499
+ *Why?* This test breaks if you change logging details, even though the actual functionality works fine.