@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,528 @@
|
|
|
1
|
+
# 1.8 Performance
|
|
2
|
+
|
|
3
|
+
Keep tests fast by avoiding expensive operations and optimizing setup/teardown. Fast tests encourage developers to run them frequently.
|
|
4
|
+
|
|
5
|
+
## Keep Tests Fast
|
|
6
|
+
|
|
7
|
+
Tests should run in milliseconds, not seconds.
|
|
8
|
+
|
|
9
|
+
**✅ Correct: fast, focused test**
|
|
10
|
+
```ts
|
|
11
|
+
it('should calculate total price', () => {
|
|
12
|
+
const order = { items: [{ price: 10 }, { price: 20 }] };
|
|
13
|
+
expect(calculateTotal(order)).toBe(30);
|
|
14
|
+
});
|
|
15
|
+
// Runs in <1ms
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**❌ Incorrect: unnecessarily slow test**
|
|
19
|
+
```ts
|
|
20
|
+
it('should calculate total price', async () => {
|
|
21
|
+
await delay(1000); // Why?
|
|
22
|
+
const order = await fetchOrderFromAPI(); // Use test data instead!
|
|
23
|
+
expect(calculateTotal(order)).toBeGreaterThan(0);
|
|
24
|
+
});
|
|
25
|
+
// Runs in >1 second
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Avoid Real Network Calls
|
|
29
|
+
|
|
30
|
+
Never make actual HTTP requests in unit tests.
|
|
31
|
+
|
|
32
|
+
**❌ Incorrect: real network calls**
|
|
33
|
+
```ts
|
|
34
|
+
it('should fetch user data', async () => {
|
|
35
|
+
const response = await fetch('https://api.example.com/users/123');
|
|
36
|
+
const user = await response.json();
|
|
37
|
+
expect(user.id).toBe('123');
|
|
38
|
+
});
|
|
39
|
+
// Slow, flaky, requires network
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**✅ Correct: mocked network calls**
|
|
43
|
+
```ts
|
|
44
|
+
it('should fetch user data', async () => {
|
|
45
|
+
const mockFetch = vi.fn().mockResolvedValue({
|
|
46
|
+
json: async () => ({ id: '123', name: 'John' }),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const user = await fetchUser('123', mockFetch);
|
|
50
|
+
expect(user.id).toBe('123');
|
|
51
|
+
});
|
|
52
|
+
// Fast, reliable, no network needed
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Minimize Setup and Teardown
|
|
56
|
+
|
|
57
|
+
Only set up what you need for each test.
|
|
58
|
+
|
|
59
|
+
**❌ Incorrect: expensive shared setup**
|
|
60
|
+
```ts
|
|
61
|
+
describe('UserService', () => {
|
|
62
|
+
let database: Database;
|
|
63
|
+
let cache: Cache;
|
|
64
|
+
let emailService: EmailService;
|
|
65
|
+
let analyticsService: AnalyticsService;
|
|
66
|
+
|
|
67
|
+
beforeEach(async () => {
|
|
68
|
+
// Setting up everything for every test!
|
|
69
|
+
database = await createRealDatabase();
|
|
70
|
+
await database.migrate();
|
|
71
|
+
cache = await createRedisCache();
|
|
72
|
+
emailService = new EmailService(await createSMTPConnection());
|
|
73
|
+
analyticsService = new AnalyticsService(await createKafkaProducer());
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should validate email format', () => {
|
|
77
|
+
// Only needs email validation, but set up entire system!
|
|
78
|
+
expect(UserService.isValidEmail('test@example.com')).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**✅ Correct: minimal setup per test**
|
|
84
|
+
```ts
|
|
85
|
+
describe('UserService', () => {
|
|
86
|
+
it('should validate email format', () => {
|
|
87
|
+
// No setup needed for pure function
|
|
88
|
+
expect(UserService.isValidEmail('test@example.com')).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should save user to database', async () => {
|
|
92
|
+
// Only set up what's needed
|
|
93
|
+
const db = new InMemoryDatabase();
|
|
94
|
+
const service = new UserService(db);
|
|
95
|
+
|
|
96
|
+
await service.saveUser({ name: 'John', email: 'john@example.com' });
|
|
97
|
+
|
|
98
|
+
const users = await db.findAll();
|
|
99
|
+
expect(users).toHaveLength(1);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Use In-Memory Implementations
|
|
105
|
+
|
|
106
|
+
Prefer in-memory fakes over real external services.
|
|
107
|
+
|
|
108
|
+
**✅ Correct: in-memory database**
|
|
109
|
+
```ts
|
|
110
|
+
class InMemoryUserRepository {
|
|
111
|
+
private users = new Map<string, User>();
|
|
112
|
+
|
|
113
|
+
async save(user: User) {
|
|
114
|
+
this.users.set(user.id, user);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async findById(id: string) {
|
|
118
|
+
return this.users.get(id) || null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
describe('UserService', () => {
|
|
123
|
+
it('should create and retrieve user', async () => {
|
|
124
|
+
const repo = new InMemoryUserRepository();
|
|
125
|
+
const service = new UserService(repo);
|
|
126
|
+
|
|
127
|
+
const created = await service.createUser({ name: 'John' });
|
|
128
|
+
const retrieved = await service.getUser(created.id);
|
|
129
|
+
|
|
130
|
+
expect(retrieved).toEqual(created);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
// Fast: no database connection, no I/O
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Avoid File System I/O
|
|
137
|
+
|
|
138
|
+
Mock file system operations in unit tests.
|
|
139
|
+
|
|
140
|
+
**❌ Incorrect: real file operations**
|
|
141
|
+
```ts
|
|
142
|
+
it('should save config to file', async () => {
|
|
143
|
+
const config = { theme: 'dark', language: 'en' };
|
|
144
|
+
await saveConfig('./test-config.json', config);
|
|
145
|
+
|
|
146
|
+
const loaded = await loadConfig('./test-config.json');
|
|
147
|
+
expect(loaded).toEqual(config);
|
|
148
|
+
|
|
149
|
+
// Clean up
|
|
150
|
+
await fs.unlink('./test-config.json');
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**✅ Correct: mocked file system**
|
|
155
|
+
```ts
|
|
156
|
+
it('should save config to file', async () => {
|
|
157
|
+
const mockFs = {
|
|
158
|
+
writeFile: vi.fn().mockResolvedValue(undefined),
|
|
159
|
+
readFile: vi.fn().mockResolvedValue('{"theme":"dark","language":"en"}'),
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const config = { theme: 'dark', language: 'en' };
|
|
163
|
+
await saveConfig('./test-config.json', config, mockFs);
|
|
164
|
+
|
|
165
|
+
expect(mockFs.writeFile).toHaveBeenCalledWith(
|
|
166
|
+
'./test-config.json',
|
|
167
|
+
JSON.stringify(config)
|
|
168
|
+
);
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Optimize Fake Timers
|
|
173
|
+
|
|
174
|
+
Use fake timers instead of real delays.
|
|
175
|
+
|
|
176
|
+
**❌ Incorrect: real delays**
|
|
177
|
+
```ts
|
|
178
|
+
it('should throttle function calls', async () => {
|
|
179
|
+
const fn = vi.fn();
|
|
180
|
+
const throttled = throttle(fn, 1000);
|
|
181
|
+
|
|
182
|
+
throttled();
|
|
183
|
+
await delay(500);
|
|
184
|
+
throttled(); // Ignored
|
|
185
|
+
await delay(600);
|
|
186
|
+
throttled(); // Called
|
|
187
|
+
|
|
188
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
189
|
+
});
|
|
190
|
+
// Takes 1.1+ seconds
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**✅ Correct: fake timers**
|
|
194
|
+
```ts
|
|
195
|
+
it('should throttle function calls', () => {
|
|
196
|
+
vi.useFakeTimers();
|
|
197
|
+
|
|
198
|
+
const fn = vi.fn();
|
|
199
|
+
const throttled = throttle(fn, 1000);
|
|
200
|
+
|
|
201
|
+
throttled();
|
|
202
|
+
vi.advanceTimersByTime(500);
|
|
203
|
+
throttled(); // Ignored
|
|
204
|
+
vi.advanceTimersByTime(600);
|
|
205
|
+
throttled(); // Called
|
|
206
|
+
|
|
207
|
+
expect(fn).toHaveBeenCalledTimes(2);
|
|
208
|
+
|
|
209
|
+
vi.useRealTimers();
|
|
210
|
+
});
|
|
211
|
+
// Runs in milliseconds
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Avoid Unnecessary Async
|
|
215
|
+
|
|
216
|
+
Don't make tests async if they don't need to be.
|
|
217
|
+
|
|
218
|
+
**❌ Incorrect: unnecessary async**
|
|
219
|
+
```ts
|
|
220
|
+
it('should add two numbers', async () => {
|
|
221
|
+
const result = add(2, 3);
|
|
222
|
+
expect(result).toBe(5);
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**✅ Correct: synchronous test**
|
|
227
|
+
```ts
|
|
228
|
+
it('should add two numbers', () => {
|
|
229
|
+
const result = add(2, 3);
|
|
230
|
+
expect(result).toBe(5);
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Batch Similar Tests
|
|
235
|
+
|
|
236
|
+
Use `it.each` to reduce setup duplication.
|
|
237
|
+
|
|
238
|
+
**❌ Incorrect: repeated setup**
|
|
239
|
+
```ts
|
|
240
|
+
describe('isValidEmail', () => {
|
|
241
|
+
it('should return true for test@example.com', () => {
|
|
242
|
+
expect(isValidEmail('test@example.com')).toBe(true);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should return true for user.name@example.co.uk', () => {
|
|
246
|
+
expect(isValidEmail('user.name@example.co.uk')).toBe(true);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should return false for invalid', () => {
|
|
250
|
+
expect(isValidEmail('invalid')).toBe(false);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should return false for @example.com', () => {
|
|
254
|
+
expect(isValidEmail('@example.com')).toBe(false);
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**✅ Correct: parameterized tests**
|
|
260
|
+
```ts
|
|
261
|
+
describe('isValidEmail', () => {
|
|
262
|
+
it.each([
|
|
263
|
+
{ email: 'test@example.com', expected: true },
|
|
264
|
+
{ email: 'user.name@example.co.uk', expected: true },
|
|
265
|
+
{ email: 'invalid', expected: false },
|
|
266
|
+
{ email: '@example.com', expected: false },
|
|
267
|
+
])('should return $expected for "$email"', ({ email, expected }) => {
|
|
268
|
+
expect(isValidEmail(email)).toBe(expected);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
// Less overhead, faster execution
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Lazy Initialization
|
|
275
|
+
|
|
276
|
+
Only create expensive objects when needed.
|
|
277
|
+
|
|
278
|
+
**❌ Incorrect: eager initialization**
|
|
279
|
+
```ts
|
|
280
|
+
describe('OrderService', () => {
|
|
281
|
+
const emailService = new EmailService(); // Created even if not used
|
|
282
|
+
const paymentGateway = new PaymentGateway(); // Created even if not used
|
|
283
|
+
|
|
284
|
+
it('should calculate order total', () => {
|
|
285
|
+
const order = { items: [{ price: 10 }] };
|
|
286
|
+
expect(OrderService.calculateTotal(order)).toBe(10);
|
|
287
|
+
// Didn't need emailService or paymentGateway!
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**✅ Correct: lazy initialization**
|
|
293
|
+
```ts
|
|
294
|
+
describe('OrderService', () => {
|
|
295
|
+
function createEmailService() {
|
|
296
|
+
return new EmailService();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function createPaymentGateway() {
|
|
300
|
+
return new PaymentGateway();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
it('should calculate order total', () => {
|
|
304
|
+
// No services created
|
|
305
|
+
const order = { items: [{ price: 10 }] };
|
|
306
|
+
expect(OrderService.calculateTotal(order)).toBe(10);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('should send confirmation email', async () => {
|
|
310
|
+
// Only create what's needed
|
|
311
|
+
const emailService = createEmailService();
|
|
312
|
+
const orderService = new OrderService(emailService);
|
|
313
|
+
|
|
314
|
+
await orderService.confirmOrder(order);
|
|
315
|
+
expect(emailService.getSentEmails()).toHaveLength(1);
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Cleanup Between Tests
|
|
321
|
+
|
|
322
|
+
Always clean up to prevent memory leaks and test pollution.
|
|
323
|
+
|
|
324
|
+
**✅ Correct: proper cleanup**
|
|
325
|
+
```ts
|
|
326
|
+
describe('EventEmitter', () => {
|
|
327
|
+
let emitter: EventEmitter;
|
|
328
|
+
|
|
329
|
+
beforeEach(() => {
|
|
330
|
+
emitter = new EventEmitter();
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
afterEach(() => {
|
|
334
|
+
emitter.removeAllListeners(); // Clean up listeners
|
|
335
|
+
vi.clearAllMocks(); // Clear mock call history
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should emit event', () => {
|
|
339
|
+
const listener = vi.fn();
|
|
340
|
+
emitter.on('test', listener);
|
|
341
|
+
emitter.emit('test');
|
|
342
|
+
expect(listener).toHaveBeenCalled();
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Use Test-Specific Data
|
|
348
|
+
|
|
349
|
+
Create minimal test data instead of large fixtures.
|
|
350
|
+
|
|
351
|
+
**❌ Incorrect: large, realistic data**
|
|
352
|
+
```ts
|
|
353
|
+
it('should validate user name', () => {
|
|
354
|
+
const user = {
|
|
355
|
+
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
356
|
+
name: 'John Doe',
|
|
357
|
+
email: 'john.doe@example.com',
|
|
358
|
+
phone: '+1-555-123-4567',
|
|
359
|
+
address: {
|
|
360
|
+
street: '123 Main St',
|
|
361
|
+
city: 'Springfield',
|
|
362
|
+
state: 'IL',
|
|
363
|
+
zip: '62701',
|
|
364
|
+
country: 'USA',
|
|
365
|
+
},
|
|
366
|
+
preferences: {
|
|
367
|
+
theme: 'dark',
|
|
368
|
+
language: 'en',
|
|
369
|
+
timezone: 'America/Chicago',
|
|
370
|
+
notifications: {
|
|
371
|
+
email: true,
|
|
372
|
+
sms: false,
|
|
373
|
+
push: true,
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
createdAt: new Date('2024-01-01'),
|
|
377
|
+
updatedAt: new Date('2024-01-15'),
|
|
378
|
+
};
|
|
379
|
+
|
|
380
|
+
expect(validateUserName(user.name)).toBe(true);
|
|
381
|
+
// Only needed name!
|
|
382
|
+
});
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
**✅ Correct: minimal test data**
|
|
386
|
+
```ts
|
|
387
|
+
it('should validate user name', () => {
|
|
388
|
+
expect(validateUserName('John Doe')).toBe(true);
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('should validate full user object', () => {
|
|
392
|
+
const user = {
|
|
393
|
+
name: 'John Doe',
|
|
394
|
+
email: 'john@example.com',
|
|
395
|
+
};
|
|
396
|
+
expect(validateUser(user)).toBe(true);
|
|
397
|
+
});
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Avoid Deep Object Comparisons
|
|
401
|
+
|
|
402
|
+
Use specific assertions instead of comparing entire objects.
|
|
403
|
+
|
|
404
|
+
**❌ Incorrect: deep comparison**
|
|
405
|
+
```ts
|
|
406
|
+
it('should update user name', async () => {
|
|
407
|
+
const user = await userService.updateName('user-123', 'Jane Doe');
|
|
408
|
+
|
|
409
|
+
expect(user).toEqual({
|
|
410
|
+
id: 'user-123',
|
|
411
|
+
name: 'Jane Doe',
|
|
412
|
+
email: 'user@example.com',
|
|
413
|
+
createdAt: expect.any(Date),
|
|
414
|
+
updatedAt: expect.any(Date),
|
|
415
|
+
preferences: { /* large nested object */ },
|
|
416
|
+
// ... many more fields
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
**✅ Correct: specific assertions**
|
|
422
|
+
```ts
|
|
423
|
+
it('should update user name', async () => {
|
|
424
|
+
const user = await userService.updateName('user-123', 'Jane Doe');
|
|
425
|
+
|
|
426
|
+
expect(user.name).toBe('Jane Doe');
|
|
427
|
+
expect(user.id).toBe('user-123');
|
|
428
|
+
});
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## Run Tests in Parallel
|
|
432
|
+
|
|
433
|
+
Vitest runs tests in parallel by default. Don't disable it unless necessary.
|
|
434
|
+
|
|
435
|
+
**✅ Correct: parallel execution (default)**
|
|
436
|
+
```ts
|
|
437
|
+
// vitest.config.ts
|
|
438
|
+
export default {
|
|
439
|
+
test: {
|
|
440
|
+
// No need to configure - parallel by default
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**❌ Incorrect: forcing sequential execution**
|
|
446
|
+
```ts
|
|
447
|
+
// vitest.config.ts
|
|
448
|
+
export default {
|
|
449
|
+
test: {
|
|
450
|
+
pool: 'forks',
|
|
451
|
+
poolOptions: {
|
|
452
|
+
threads: {
|
|
453
|
+
singleThread: true, // Slow!
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
## Isolate Test Files
|
|
461
|
+
|
|
462
|
+
Avoid shared global state between test files.
|
|
463
|
+
|
|
464
|
+
**❌ Incorrect: shared mutable state**
|
|
465
|
+
```ts
|
|
466
|
+
// global-state.ts
|
|
467
|
+
export const cache = new Map();
|
|
468
|
+
|
|
469
|
+
// test1.test.ts
|
|
470
|
+
import { cache } from './global-state';
|
|
471
|
+
|
|
472
|
+
it('should add item to cache', () => {
|
|
473
|
+
cache.set('key', 'value');
|
|
474
|
+
expect(cache.get('key')).toBe('value');
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
// test2.test.ts
|
|
478
|
+
import { cache } from './global-state';
|
|
479
|
+
|
|
480
|
+
it('should have empty cache', () => {
|
|
481
|
+
expect(cache.size).toBe(0); // Fails if test1 runs first!
|
|
482
|
+
});
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
**✅ Correct: isolated state**
|
|
486
|
+
```ts
|
|
487
|
+
// test1.test.ts
|
|
488
|
+
it('should add item to cache', () => {
|
|
489
|
+
const cache = new Map();
|
|
490
|
+
cache.set('key', 'value');
|
|
491
|
+
expect(cache.get('key')).toBe('value');
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
// test2.test.ts
|
|
495
|
+
it('should have empty cache', () => {
|
|
496
|
+
const cache = new Map();
|
|
497
|
+
expect(cache.size).toBe(0);
|
|
498
|
+
});
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
## Monitor Test Performance
|
|
502
|
+
|
|
503
|
+
Identify and fix slow tests using Vitest's reporter.
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
# Run tests with timing information
|
|
507
|
+
vitest --reporter=verbose
|
|
508
|
+
|
|
509
|
+
# Find slow tests
|
|
510
|
+
vitest --reporter=default --slow-test-threshold=1000
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**✅ Correct: refactor slow tests**
|
|
514
|
+
```ts
|
|
515
|
+
// Before: 5 seconds
|
|
516
|
+
it('should process large dataset', async () => {
|
|
517
|
+
const data = await fetchLargeDataset(); // Slow API call
|
|
518
|
+
const result = processData(data);
|
|
519
|
+
expect(result.length).toBeGreaterThan(0);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
// After: <10ms
|
|
523
|
+
it('should process large dataset', () => {
|
|
524
|
+
const data = createMockDataset(1000); // Fast mock data
|
|
525
|
+
const result = processData(data);
|
|
526
|
+
expect(result.length).toBe(1000);
|
|
527
|
+
});
|
|
528
|
+
```
|