@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,260 @@
1
+ # 1.2 AAA Pattern
2
+
3
+ Structure tests as **Arrange**, **Act**, **Assert** for maximum clarity and readability.
4
+
5
+ ## What is AAA?
6
+
7
+ - **Arrange**: Set up the test data, dependencies, and preconditions
8
+ - **Act**: Execute the code under test
9
+ - **Assert**: Verify the expected outcome
10
+
11
+ This pattern makes tests instantly understandable by separating setup, execution, and verification.
12
+
13
+ ## Basic AAA Structure
14
+
15
+ **❌ Incorrect: mixed arrange/act/assert**
16
+ ```ts
17
+ it('should return the default value for an unknown property', () => {
18
+ const defaultColor: Color = [128, 128, 128, 155];
19
+ const colorLookup = lookup(colorTable, defaultVal(defaultColor));
20
+ const actual = colorLookup('UNKNOWN');
21
+ expect(actual).toEqual(defaultColor);
22
+
23
+ // Another test mixed in!
24
+ const result2 = colorLookup('ANOTHER');
25
+ expect(result2).toEqual(defaultColor);
26
+ });
27
+ ```
28
+
29
+ **✅ Correct: clear AAA structure with comments**
30
+ ```ts
31
+ it('should return the default value for an unknown property', () => {
32
+ // Arrange
33
+ const defaultColor: Color = [128, 128, 128, 155];
34
+ const colorLookup = lookup(colorTable, defaultVal(defaultColor));
35
+
36
+ // Act
37
+ const actual = colorLookup('UNKNOWN');
38
+
39
+ // Assert
40
+ expect(actual).toEqual(defaultColor);
41
+ });
42
+ ```
43
+ *Why?* Without clear separation and with multiple behaviors tested together, it's hard to understand what's being tested and why a test fails.
44
+
45
+ ## Blank Lines for Separation
46
+
47
+ Use blank lines between AAA sections even without comments for simple tests.
48
+
49
+ **❌ Incorrect: no visual separation**
50
+ ```ts
51
+ it('should calculate total price with tax', () => {
52
+ const cart = new ShoppingCart();
53
+ cart.addItem({ name: 'Widget', price: 100 });
54
+ const total = cart.calculateTotal(0.08);
55
+ expect(total).toEqual(108);
56
+ });
57
+ ```
58
+
59
+ **✅ Correct: visual separation with blank lines**
60
+ ```ts
61
+ it('should calculate total price with tax', () => {
62
+ const cart = new ShoppingCart();
63
+ cart.addItem({ name: 'Widget', price: 100 });
64
+
65
+ const total = cart.calculateTotal(0.08);
66
+
67
+ expect(total).toEqual(108);
68
+ });
69
+ ```
70
+ *Why?* Without visual separation, it's harder to quickly identify where setup ends and the actual test logic begins.
71
+
72
+ ## Multiple Assertions for Same Behavior
73
+
74
+ Multiple assertions are OK when they verify different aspects of the **same behavior**.
75
+
76
+ **❌ Incorrect: testing multiple unrelated behaviors**
77
+ ```ts
78
+ it('should handle user operations', () => {
79
+ // Testing creation
80
+ const user = createUser({ email: 'test@example.com' });
81
+ expect(user.email).toEqual('test@example.com');
82
+
83
+ // Testing update (different behavior!)
84
+ updateUser(user.id, { name: 'Updated' });
85
+ expect(user.name).toEqual('Updated');
86
+
87
+ // Testing deletion (different behavior!)
88
+ deleteUser(user.id);
89
+ expect(getUser(user.id)).toBeNull();
90
+ });
91
+ ```
92
+
93
+ **✅ Correct: multiple assertions for one behavior**
94
+ ```ts
95
+ it('should create user with all required fields', () => {
96
+ // Arrange
97
+ const userData = { email: 'test@example.com', name: 'Test User' };
98
+
99
+ // Act
100
+ const user = createUser(userData);
101
+
102
+ // Assert
103
+ expect(user.email).toEqual('test@example.com');
104
+ expect(user.name).toEqual('Test User');
105
+ expect(user.id).toBeDefined();
106
+ expect(user.createdAt).toBeInstanceOf(Date);
107
+ });
108
+ ```
109
+ *Why?* Each test should verify one behavior. Split this into three separate tests: creation, update, and deletion.
110
+
111
+ ## Avoid Logic in Tests
112
+
113
+ Keep the AAA sections simple - avoid conditional logic, loops, or complex calculations.
114
+
115
+ **❌ Incorrect: complex logic in test**
116
+ ```ts
117
+ it('should filter active users', () => {
118
+ const users = generateUsers(100); // Hidden complexity
119
+ const userService = new UserService(users);
120
+ const activeUsers = userService.getActiveUsers();
121
+
122
+ // Complex verification logic
123
+ let count = 0;
124
+ for (const user of users) {
125
+ if (user.active) {
126
+ expect(activeUsers).toContain(user);
127
+ count++;
128
+ }
129
+ }
130
+ expect(activeUsers).toHaveLength(count);
131
+ });
132
+ ```
133
+
134
+ **✅ Correct: straightforward test logic**
135
+ ```ts
136
+ it('should filter active users', () => {
137
+ // Arrange
138
+ const users = [
139
+ { id: 1, name: 'Alice', active: true },
140
+ { id: 2, name: 'Bob', active: false },
141
+ { id: 3, name: 'Charlie', active: true },
142
+ ];
143
+ const userService = new UserService(users);
144
+
145
+ // Act
146
+ const activeUsers = userService.getActiveUsers();
147
+
148
+ // Assert
149
+ expect(activeUsers).toHaveLength(2);
150
+ expect(activeUsers[0].name).toEqual('Alice');
151
+ expect(activeUsers[1].name).toEqual('Charlie');
152
+ });
153
+ ```
154
+ *Why?* If the test has bugs, you won't know if the test or the code is wrong. Keep tests simple and obvious.
155
+
156
+ ## Complex Arrange Sections
157
+
158
+ For complex setup, extract to helper functions or factories.
159
+
160
+ **❌ Incorrect: complex setup in test**
161
+ ```ts
162
+ it('should apply discount to orders over $50', () => {
163
+ // Arrange - too much going on!
164
+ const order = new Order();
165
+ const items = [];
166
+ for (let i = 0; i < 10; i++) {
167
+ const item = {
168
+ id: i,
169
+ name: `Item ${i}`,
170
+ price: 10 * i,
171
+ category: i % 2 === 0 ? 'even' : 'odd',
172
+ taxable: i > 5,
173
+ };
174
+ items.push(item);
175
+ order.addItem(item);
176
+ }
177
+ const discountService = new DiscountService();
178
+
179
+ // Act
180
+ const discountedTotal = discountService.applyDiscount(order);
181
+
182
+ // Assert
183
+ expect(discountedTotal).toBeLessThan(order.total);
184
+ });
185
+ ```
186
+
187
+ **✅ Correct: extracted setup helper**
188
+ ```ts
189
+ function createTestOrder(items: number = 3): Order {
190
+ const order = new Order();
191
+ for (let i = 0; i < items; i++) {
192
+ order.addItem({ id: i, name: `Item ${i}`, price: 10 * i });
193
+ }
194
+ return order;
195
+ }
196
+
197
+ it('should apply discount to orders over $50', () => {
198
+ // Arrange
199
+ const order = createTestOrder(10);
200
+ const discountService = new DiscountService();
201
+
202
+ // Act
203
+ const discountedTotal = discountService.applyDiscount(order);
204
+
205
+ // Assert
206
+ expect(discountedTotal).toBeLessThan(order.total);
207
+ expect(discountService.discountApplied).toEqual(0.1);
208
+ });
209
+ ```
210
+ *Why?* Complex setup obscures the test's purpose. Extract to helper functions or test-utils.
211
+
212
+ ## Async/Await with AAA
213
+
214
+ AAA works perfectly with async tests - just add await in the Act section.
215
+
216
+ **❌ Incorrect: mixing setup with async calls**
217
+ ```ts
218
+ it('should fetch user from API', async () => {
219
+ const userId = 'user-123';
220
+ const apiClient = new ApiClient();
221
+ const user = await apiClient.getUser(userId); // Act hidden in middle
222
+ const profile = await apiClient.getProfile(userId); // More acts!
223
+ expect(user.id).toEqual(userId);
224
+ expect(profile.userId).toEqual(userId);
225
+ });
226
+ ```
227
+
228
+ **✅ Correct: async AAA pattern**
229
+ ```ts
230
+ it('should fetch user from API', async () => {
231
+ // Arrange
232
+ const userId = 'user-123';
233
+ const apiClient = new ApiClient();
234
+
235
+ // Act
236
+ const user = await apiClient.getUser(userId);
237
+
238
+ // Assert
239
+ expect(user.id).toEqual(userId);
240
+ expect(user.name).toBeDefined();
241
+ });
242
+ ```
243
+ *Why?* Multiple async operations without clear separation make it unclear what's being tested.
244
+
245
+ ## When to Omit AAA Comments
246
+
247
+ For very simple tests, AAA comments can be omitted if blank lines provide sufficient clarity.
248
+
249
+ **✅ Correct: simple test without AAA comments**
250
+ ```ts
251
+ it('should add two numbers', () => {
252
+ const calculator = new Calculator();
253
+
254
+ const result = calculator.add(2, 3);
255
+
256
+ expect(result).toEqual(5);
257
+ });
258
+ ```
259
+
260
+ However, **when in doubt, include the comments**. They never hurt and help onboarding developers understand your test structure.
@@ -0,0 +1,393 @@
1
+ # 1.5 Assertions
2
+
3
+ Use strict, precise assertions that verify exact behavior. Loose assertions can pass even when code is wrong.
4
+
5
+ ## Equality Assertions
6
+
7
+ Prefer `toEqual` over `toBe` for objects and arrays. Use `toStrictEqual` when you need to verify undefined properties.
8
+
9
+ **✅ Correct: toEqual for objects**
10
+ ```ts
11
+ it('should return user object with correct properties', () => {
12
+ const user = createUser({ name: 'John', email: 'john@example.com' });
13
+
14
+ expect(user).toEqual({
15
+ name: 'John',
16
+ email: 'john@example.com',
17
+ id: expect.any(String),
18
+ createdAt: expect.any(Date),
19
+ });
20
+ });
21
+ ```
22
+
23
+ **❌ Incorrect: toBe for objects**
24
+ ```ts
25
+ it('should return user object', () => {
26
+ const user = createUser({ name: 'John' });
27
+ expect(user).toBe({ name: 'John' }); // Always fails - different references!
28
+ });
29
+ ```
30
+ *Why?* `toBe` uses `Object.is()` reference equality. For objects/arrays, use `toEqual`.
31
+
32
+ ## toEqual vs toStrictEqual
33
+
34
+ Use `toStrictEqual` when you need to verify that undefined properties don't exist.
35
+
36
+ **✅ Correct: toStrictEqual catches undefined properties**
37
+ ```ts
38
+ it('should not include undefined properties', () => {
39
+ const user = { name: 'John', email: 'john@example.com' };
40
+
41
+ expect(user).toStrictEqual({ name: 'John', email: 'john@example.com' });
42
+ });
43
+ ```
44
+
45
+ **⚠️ Potential issue: toEqual ignores undefined**
46
+ ```ts
47
+ it('may not catch unexpected undefined', () => {
48
+ const user = { name: 'John', email: undefined };
49
+
50
+ // This passes with toEqual!
51
+ expect(user).toEqual({ name: 'John' });
52
+
53
+ // This fails with toStrictEqual (better)
54
+ expect(user).toStrictEqual({ name: 'John' }); // Fails - email is undefined
55
+ });
56
+ ```
57
+
58
+ ## Primitives: toBe vs toEqual
59
+
60
+ For primitives (numbers, strings, booleans), both work, but `toBe` is more semantically correct.
61
+
62
+ **✅ Correct: toBe for primitives**
63
+ ```ts
64
+ expect(count).toBe(5);
65
+ expect(name).toBe('John');
66
+ expect(isValid).toBe(true);
67
+ ```
68
+
69
+ **✅ Also correct but less semantic: toEqual for primitives**
70
+ ```ts
71
+ expect(count).toEqual(5); // Works but toBe is clearer for primitives
72
+ ```
73
+
74
+ ## Avoid Loose Assertions
75
+
76
+ Don't use fuzzy matchers when you can be precise.
77
+
78
+ **❌ Incorrect: loose assertion**
79
+ ```ts
80
+ it('should return user data', () => {
81
+ const result = fetchUser('123');
82
+ expect(result).toContain('john'); // Too vague!
83
+ });
84
+ ```
85
+
86
+ **✅ Correct: precise assertion**
87
+ ```ts
88
+ it('should return user with email', () => {
89
+ const result = fetchUser('123');
90
+ expect(result).toEqual({
91
+ id: '123',
92
+ name: 'John Doe',
93
+ email: 'john@example.com',
94
+ });
95
+ });
96
+ ```
97
+
98
+ ## Array Assertions
99
+
100
+ Use specific array matchers for clarity.
101
+
102
+ **✅ Correct: specific array matchers**
103
+ ```ts
104
+ describe('filterActiveUsers', () => {
105
+ it('should return array of active users only', () => {
106
+ const users = [
107
+ { id: 1, name: 'Alice', active: true },
108
+ { id: 2, name: 'Bob', active: false },
109
+ { id: 3, name: 'Charlie', active: true },
110
+ ];
111
+
112
+ const result = filterActiveUsers(users);
113
+
114
+ expect(result).toHaveLength(2);
115
+ expect(result).toEqual([
116
+ { id: 1, name: 'Alice', active: true },
117
+ { id: 3, name: 'Charlie', active: true },
118
+ ]);
119
+ });
120
+
121
+ it('should contain specific user', () => {
122
+ const result = getAllUsers();
123
+ expect(result).toContainEqual({ id: 1, name: 'Alice', active: true });
124
+ });
125
+
126
+ it('should return empty array when no active users', () => {
127
+ const users = [{ id: 1, name: 'Bob', active: false }];
128
+ expect(filterActiveUsers(users)).toEqual([]);
129
+ });
130
+ });
131
+ ```
132
+
133
+ **❌ Incorrect: imprecise array checks**
134
+ ```ts
135
+ it('should return users', () => {
136
+ const result = filterActiveUsers(users);
137
+ expect(result.length).toBeGreaterThan(0); // How many? Which users?
138
+ });
139
+ ```
140
+
141
+ ## String Assertions
142
+
143
+ Use appropriate string matchers based on what you're testing.
144
+
145
+ **✅ Correct: precise string assertions**
146
+ ```ts
147
+ describe('formatName', () => {
148
+ it('should return full name', () => {
149
+ expect(formatName('john', 'doe')).toBe('John Doe');
150
+ });
151
+
152
+ it('should include title when provided', () => {
153
+ const result = formatName('john', 'doe', 'Dr.');
154
+ expect(result).toBe('Dr. John Doe');
155
+ });
156
+
157
+ it('should match name pattern', () => {
158
+ const result = formatName('john', 'doe');
159
+ expect(result).toMatch(/^[A-Z][a-z]+ [A-Z][a-z]+$/);
160
+ });
161
+
162
+ it('should contain first name', () => {
163
+ const result = formatName('john', 'doe');
164
+ expect(result).toContain('John');
165
+ });
166
+ });
167
+ ```
168
+
169
+ **❌ Incorrect: overly loose string checks**
170
+ ```ts
171
+ it('should format name', () => {
172
+ const result = formatName('john', 'doe');
173
+ expect(result).toBeTruthy(); // Way too vague!
174
+ expect(result.length).toBeGreaterThan(0); // Still vague!
175
+ });
176
+ ```
177
+
178
+ ## Number Assertions
179
+
180
+ Use comparison matchers for numeric ranges and boundaries.
181
+
182
+ **✅ Correct: numeric matchers**
183
+ ```ts
184
+ describe('calculateDiscount', () => {
185
+ it('should return positive discount', () => {
186
+ const discount = calculateDiscount(100, 0.1);
187
+ expect(discount).toBeGreaterThan(0);
188
+ expect(discount).toBeLessThanOrEqual(100);
189
+ });
190
+
191
+ it('should return exact discount amount', () => {
192
+ expect(calculateDiscount(100, 0.1)).toBe(10);
193
+ });
194
+
195
+ it('should handle floating point comparison', () => {
196
+ const result = calculateTax(99.99, 0.0825);
197
+ expect(result).toBeCloseTo(8.25, 2); // Within 2 decimal places
198
+ });
199
+ });
200
+ ```
201
+
202
+ ## Boolean and Nullish Assertions
203
+
204
+ Be explicit about boolean, null, and undefined checks.
205
+
206
+ **✅ Correct: explicit boolean checks**
207
+ ```ts
208
+ describe('isValidEmail', () => {
209
+ it('should return true for valid email', () => {
210
+ expect(isValidEmail('test@example.com')).toBe(true);
211
+ });
212
+
213
+ it('should return false for invalid email', () => {
214
+ expect(isValidEmail('invalid')).toBe(false);
215
+ });
216
+ });
217
+
218
+ describe('findUser', () => {
219
+ it('should return null when user not found', () => {
220
+ expect(findUser('invalid-id')).toBeNull();
221
+ });
222
+
223
+ it('should return undefined for missing optional field', () => {
224
+ const user = createUser({ name: 'John' });
225
+ expect(user.middleName).toBeUndefined();
226
+ });
227
+
228
+ it('should have defined email', () => {
229
+ const user = createUser({ name: 'John', email: 'john@example.com' });
230
+ expect(user.email).toBeDefined();
231
+ });
232
+ });
233
+ ```
234
+
235
+ **❌ Incorrect: loose truthy/falsy checks**
236
+ ```ts
237
+ it('should validate email', () => {
238
+ expect(isValidEmail('test@example.com')).toBeTruthy(); // Could be any truthy value!
239
+ });
240
+
241
+ it('should not find user', () => {
242
+ expect(findUser('invalid')).toBeFalsy(); // Could be false, null, undefined, 0, etc.
243
+ });
244
+ ```
245
+ *Why?* `toBeTruthy`/`toBeFalsy` are too permissive. Be explicit about the expected value.
246
+
247
+ ## Object Property Assertions
248
+
249
+ Use matchers that verify object structure and properties.
250
+
251
+ **✅ Correct: object property matchers**
252
+ ```ts
253
+ describe('createUser', () => {
254
+ it('should have required properties', () => {
255
+ const user = createUser({ name: 'John', email: 'john@example.com' });
256
+
257
+ expect(user).toHaveProperty('id');
258
+ expect(user).toHaveProperty('name', 'John');
259
+ expect(user).toHaveProperty('email', 'john@example.com');
260
+ expect(user).toHaveProperty('createdAt');
261
+ });
262
+
263
+ it('should match expected shape', () => {
264
+ const user = createUser({ name: 'John', email: 'john@example.com' });
265
+
266
+ expect(user).toMatchObject({
267
+ name: 'John',
268
+ email: 'john@example.com',
269
+ });
270
+ // toMatchObject allows extra properties like id, createdAt
271
+ });
272
+ });
273
+ ```
274
+
275
+ ## Type Assertions
276
+
277
+ Verify types when type checking is important.
278
+
279
+ **✅ Correct: type assertions**
280
+ ```ts
281
+ describe('parseData', () => {
282
+ it('should return correct types', () => {
283
+ const result = parseData('{"count": 5}');
284
+
285
+ expect(result.count).toEqual(expect.any(Number));
286
+ expect(result.timestamp).toEqual(expect.any(Date));
287
+ expect(result.tags).toEqual(expect.any(Array));
288
+ });
289
+
290
+ it('should return string array', () => {
291
+ const tags = getTags();
292
+
293
+ expect(tags).toEqual(expect.arrayContaining([expect.any(String)]));
294
+ });
295
+ });
296
+ ```
297
+
298
+ ## Asymmetric Matchers
299
+
300
+ Use asymmetric matchers when exact values aren't known but structure is.
301
+
302
+ **✅ Correct: asymmetric matchers for dynamic values**
303
+ ```ts
304
+ describe('createOrder', () => {
305
+ it('should create order with generated ID', () => {
306
+ const order = createOrder({ items: [{ id: 1, quantity: 2 }] });
307
+
308
+ expect(order).toEqual({
309
+ id: expect.stringMatching(/^order-[a-f0-9]+$/),
310
+ items: expect.arrayContaining([
311
+ expect.objectContaining({ id: 1, quantity: 2 }),
312
+ ]),
313
+ createdAt: expect.any(Date),
314
+ status: 'pending',
315
+ });
316
+ });
317
+ });
318
+ ```
319
+
320
+ ## Negation
321
+
322
+ Use `.not` to assert something is NOT true, but be specific.
323
+
324
+ **✅ Correct: specific negation**
325
+ ```ts
326
+ it('should not include deleted users', () => {
327
+ const users = getActiveUsers();
328
+
329
+ expect(users).not.toContainEqual(
330
+ expect.objectContaining({ status: 'deleted' })
331
+ );
332
+ });
333
+
334
+ it('should not be empty string', () => {
335
+ const username = generateUsername();
336
+ expect(username).not.toBe('');
337
+ expect(username.length).toBeGreaterThan(0);
338
+ });
339
+ ```
340
+
341
+ **❌ Incorrect: vague negation**
342
+ ```ts
343
+ it('should not be wrong', () => {
344
+ expect(result).not.toBeFalsy(); // What IS it then?
345
+ });
346
+ ```
347
+
348
+ ## Custom Error Messages
349
+
350
+ Add custom messages to clarify assertion failures.
351
+
352
+ **✅ Correct: custom error messages for complex assertions**
353
+ ```ts
354
+ it('should process all items', () => {
355
+ const result = processItems(items);
356
+
357
+ expect(
358
+ result.every(item => item.processed === true),
359
+ 'All items should have processed=true'
360
+ ).toBe(true);
361
+ });
362
+ ```
363
+
364
+ ## Common Assertion Anti-Patterns
365
+
366
+ **❌ Incorrect: no assertion**
367
+ ```ts
368
+ it('should create user', () => {
369
+ createUser({ name: 'John' });
370
+ // No assertion! Test always passes!
371
+ });
372
+ ```
373
+
374
+ **❌ Incorrect: asserting implementation details**
375
+ ```ts
376
+ it('should call internal helper', () => {
377
+ const spy = vi.spyOn(service, '_internalHelper');
378
+ service.publicMethod();
379
+ expect(spy).toHaveBeenCalled(); // Testing internal implementation
380
+ });
381
+ ```
382
+ *Why?* Test behavior, not implementation. Internal helpers can change without breaking public API.
383
+
384
+ **❌ Incorrect: multiple unrelated assertions**
385
+ ```ts
386
+ it('should work', () => {
387
+ const user = createUser({ name: 'John' });
388
+ expect(user.name).toBe('John');
389
+
390
+ const product = createProduct({ name: 'Widget' });
391
+ expect(product.name).toBe('Widget'); // Different concern - split into separate test
392
+ });
393
+ ```