specweave 0.23.18 → 0.24.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 (167) hide show
  1. package/.claude-plugin/marketplace.json +93 -49
  2. package/CLAUDE.md +137 -4
  3. package/dist/src/cli/helpers/ado-area-path-mapper.d.ts +89 -0
  4. package/dist/src/cli/helpers/ado-area-path-mapper.d.ts.map +1 -0
  5. package/dist/src/cli/helpers/ado-area-path-mapper.js +213 -0
  6. package/dist/src/cli/helpers/ado-area-path-mapper.js.map +1 -0
  7. package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.d.ts +29 -0
  8. package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.d.ts.map +1 -0
  9. package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.js +109 -0
  10. package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.js.map +1 -0
  11. package/dist/src/cli/helpers/issue-tracker/ado.d.ts +1 -0
  12. package/dist/src/cli/helpers/issue-tracker/ado.d.ts.map +1 -1
  13. package/dist/src/cli/helpers/issue-tracker/ado.js +2 -0
  14. package/dist/src/cli/helpers/issue-tracker/ado.js.map +1 -1
  15. package/dist/src/cli/helpers/smart-filter.d.ts +83 -0
  16. package/dist/src/cli/helpers/smart-filter.d.ts.map +1 -0
  17. package/dist/src/cli/helpers/smart-filter.js +265 -0
  18. package/dist/src/cli/helpers/smart-filter.js.map +1 -0
  19. package/dist/src/core/qa/quality-gate-decider.d.ts +1 -1
  20. package/dist/src/core/qa/quality-gate-decider.js +2 -2
  21. package/dist/src/core/qa/quality-gate-decider.js.map +1 -1
  22. package/dist/src/core/qa/risk-calculator.d.ts +2 -2
  23. package/dist/src/core/qa/risk-calculator.js +2 -2
  24. package/dist/src/core/validators/ac-presence-validator.d.ts +56 -0
  25. package/dist/src/core/validators/ac-presence-validator.d.ts.map +1 -0
  26. package/dist/src/core/validators/ac-presence-validator.js +149 -0
  27. package/dist/src/core/validators/ac-presence-validator.js.map +1 -0
  28. package/dist/src/integrations/ado/area-path-mapper.d.ts +137 -0
  29. package/dist/src/integrations/ado/area-path-mapper.d.ts.map +1 -0
  30. package/dist/src/integrations/ado/area-path-mapper.js +267 -0
  31. package/dist/src/integrations/ado/area-path-mapper.js.map +1 -0
  32. package/dist/src/integrations/jira/filter-processor.d.ts +126 -0
  33. package/dist/src/integrations/jira/filter-processor.d.ts.map +1 -0
  34. package/dist/src/integrations/jira/filter-processor.js +207 -0
  35. package/dist/src/integrations/jira/filter-processor.js.map +1 -0
  36. package/dist/src/integrations/jira/jira-client.d.ts +13 -0
  37. package/dist/src/integrations/jira/jira-client.d.ts.map +1 -1
  38. package/dist/src/integrations/jira/jira-client.js +33 -0
  39. package/dist/src/integrations/jira/jira-client.js.map +1 -1
  40. package/dist/src/utils/ac-embedder.d.ts +63 -0
  41. package/dist/src/utils/ac-embedder.d.ts.map +1 -0
  42. package/dist/src/utils/ac-embedder.js +217 -0
  43. package/dist/src/utils/ac-embedder.js.map +1 -0
  44. package/dist/src/utils/env-manager.d.ts +86 -0
  45. package/dist/src/utils/env-manager.d.ts.map +1 -0
  46. package/dist/src/utils/env-manager.js +188 -0
  47. package/dist/src/utils/env-manager.js.map +1 -0
  48. package/package.json +1 -1
  49. package/plugins/specweave/.claude-plugin/plugin.json +1 -1
  50. package/plugins/specweave/agents/AGENTS-INDEX.md +1 -1
  51. package/plugins/specweave/agents/increment-quality-judge-v2/AGENT.md +9 -9
  52. package/plugins/specweave/commands/specweave-do.md +37 -0
  53. package/plugins/specweave/commands/specweave-done.md +159 -0
  54. package/plugins/specweave/commands/specweave-embed-acs.md +446 -0
  55. package/plugins/specweave/commands/specweave-next.md +148 -3
  56. package/plugins/specweave/commands/specweave-qa.md +2 -2
  57. package/plugins/specweave/hooks/pre-increment-start.sh +168 -0
  58. package/plugins/specweave/skills/SKILLS-INDEX.md +1 -1
  59. package/plugins/specweave-ado/.claude-plugin/plugin.json +1 -1
  60. package/plugins/specweave-ado/commands/specweave-ado-import-projects.md +331 -0
  61. package/plugins/specweave-alternatives/.claude-plugin/plugin.json +10 -0
  62. package/plugins/specweave-alternatives/commands/alternatives-analyze.md +336 -0
  63. package/plugins/specweave-alternatives/skills/architecture-alternatives/SKILL.md +651 -0
  64. package/plugins/specweave-alternatives/skills/bmad-method/SKILL.md +420 -0
  65. package/plugins/specweave-alternatives/skills/spec-kit-expert/SKILL.md +487 -0
  66. package/plugins/specweave-backend/commands/api-scaffold.md +80 -0
  67. package/plugins/specweave-backend/commands/crud-generate.md +109 -0
  68. package/plugins/specweave-backend/commands/migration-generate.md +139 -0
  69. package/plugins/specweave-confluent/commands/connector-deploy.md +154 -0
  70. package/plugins/specweave-confluent/commands/ksqldb-query.md +179 -0
  71. package/plugins/specweave-confluent/commands/schema-register.md +123 -0
  72. package/plugins/specweave-core/.claude-plugin/plugin.json +21 -0
  73. package/plugins/specweave-core/commands/architecture-review.md +288 -0
  74. package/plugins/specweave-core/commands/code-review.md +213 -0
  75. package/plugins/specweave-core/commands/refactor-plan.md +249 -0
  76. package/plugins/specweave-core/skills/code-quality/SKILL.md +157 -0
  77. package/plugins/specweave-core/skills/design-patterns/SKILL.md +244 -0
  78. package/plugins/specweave-core/skills/software-architecture/SKILL.md +83 -0
  79. package/plugins/specweave-cost-optimizer/.claude-plugin/plugin.json +22 -0
  80. package/plugins/specweave-cost-optimizer/commands/cost-analyze.md +360 -0
  81. package/plugins/specweave-cost-optimizer/commands/cost-optimize.md +480 -0
  82. package/plugins/specweave-cost-optimizer/skills/aws-cost-expert/SKILL.md +416 -0
  83. package/plugins/specweave-cost-optimizer/skills/cloud-pricing/SKILL.md +325 -0
  84. package/plugins/specweave-cost-optimizer/skills/cost-optimization/SKILL.md +337 -0
  85. package/plugins/specweave-diagrams/.claude-plugin/plugin.json +1 -1
  86. package/plugins/specweave-diagrams/commands/diagrams-generate.md +168 -0
  87. package/plugins/specweave-docs/.claude-plugin/plugin.json +10 -0
  88. package/plugins/specweave-docs/commands/docs-generate.md +441 -0
  89. package/plugins/specweave-docs/commands/docs-init.md +334 -0
  90. package/plugins/specweave-docs/skills/docusaurus/SKILL.md +581 -0
  91. package/plugins/specweave-docs/skills/spec-driven-brainstorming/SKILL.md +689 -0
  92. package/plugins/specweave-docs/skills/technical-writing/SKILL.md +1039 -0
  93. package/plugins/specweave-docs-preview/.claude-plugin/plugin.json +1 -1
  94. package/plugins/specweave-figma/.claude-plugin/plugin.json +23 -0
  95. package/plugins/specweave-figma/commands/figma-import.md +690 -0
  96. package/plugins/specweave-figma/commands/figma-to-react.md +834 -0
  97. package/plugins/specweave-figma/commands/figma-tokens.md +815 -0
  98. package/plugins/specweave-frontend/.claude-plugin/plugin.json +21 -0
  99. package/plugins/specweave-frontend/agents/frontend-architect/AGENT.md +387 -0
  100. package/plugins/specweave-frontend/agents/frontend-architect/README.md +385 -0
  101. package/plugins/specweave-frontend/agents/frontend-architect/examples.md +590 -0
  102. package/plugins/specweave-frontend/agents/frontend-architect/templates/component-template.tsx +152 -0
  103. package/plugins/specweave-frontend/agents/frontend-architect/templates/hook-template.ts +311 -0
  104. package/plugins/specweave-frontend/agents/frontend-architect/templates/page-template.tsx +228 -0
  105. package/plugins/specweave-frontend/commands/component-generate.md +510 -0
  106. package/plugins/specweave-frontend/commands/design-system-init.md +494 -0
  107. package/plugins/specweave-frontend/commands/frontend-scaffold.md +207 -0
  108. package/plugins/specweave-frontend/commands/nextjs-setup.md +396 -0
  109. package/plugins/specweave-frontend/skills/design-system-architect/SKILL.md +278 -0
  110. package/plugins/specweave-frontend/skills/frontend/SKILL.md +420 -0
  111. package/plugins/specweave-frontend/skills/nextjs/SKILL.md +546 -0
  112. package/plugins/specweave-github/.claude-plugin/plugin.json +1 -1
  113. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +194 -0
  114. package/plugins/specweave-infrastructure/.claude-plugin/plugin.json +1 -1
  115. package/plugins/specweave-jira/.claude-plugin/plugin.json +1 -1
  116. package/plugins/specweave-jira/commands/import-projects.js +183 -0
  117. package/plugins/specweave-jira/commands/import-projects.md +97 -0
  118. package/plugins/specweave-jira/commands/import-projects.ts +288 -0
  119. package/plugins/specweave-jira/commands/specweave-jira-import-projects.md +298 -0
  120. package/plugins/specweave-kafka/.claude-plugin/plugin.json +1 -1
  121. package/plugins/specweave-kafka-streams/.claude-plugin/plugin.json +1 -1
  122. package/plugins/specweave-kubernetes/commands/cluster-setup.md +262 -0
  123. package/plugins/specweave-kubernetes/commands/deployment-generate.md +242 -0
  124. package/plugins/specweave-kubernetes/commands/helm-scaffold.md +333 -0
  125. package/plugins/specweave-ml/.claude-plugin/plugin.json +1 -1
  126. package/plugins/specweave-mobile/commands/app-scaffold.md +233 -0
  127. package/plugins/specweave-mobile/commands/build-config.md +256 -0
  128. package/plugins/specweave-mobile/commands/screen-generate.md +289 -0
  129. package/plugins/specweave-n8n/.claude-plugin/plugin.json +1 -1
  130. package/plugins/specweave-plugin-dev/.claude-plugin/plugin.json +13 -12
  131. package/plugins/specweave-plugin-dev/commands/plugin-create.md +333 -0
  132. package/plugins/specweave-plugin-dev/commands/plugin-publish.md +339 -0
  133. package/plugins/specweave-plugin-dev/commands/plugin-test.md +293 -0
  134. package/plugins/specweave-plugin-dev/skills/claude-sdk/SKILL.md +162 -0
  135. package/plugins/specweave-plugin-dev/skills/marketplace-publishing/SKILL.md +263 -0
  136. package/plugins/specweave-plugin-dev/skills/plugin-development/SKILL.md +316 -0
  137. package/plugins/specweave-release/.claude-plugin/plugin.json +1 -1
  138. package/plugins/specweave-release/commands/specweave-release-npm.md +110 -0
  139. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +168 -0
  140. package/plugins/specweave-testing/.claude-plugin/plugin.json +21 -0
  141. package/plugins/specweave-testing/agents/qa-engineer/AGENT.md +797 -0
  142. package/plugins/specweave-testing/agents/qa-engineer/README.md +443 -0
  143. package/plugins/specweave-testing/agents/qa-engineer/templates/playwright-e2e-test.ts +470 -0
  144. package/plugins/specweave-testing/agents/qa-engineer/templates/test-data-factory.ts +507 -0
  145. package/plugins/specweave-testing/agents/qa-engineer/templates/vitest-unit-test.ts +400 -0
  146. package/plugins/specweave-testing/agents/qa-engineer/test-strategies.md +726 -0
  147. package/plugins/specweave-testing/commands/e2e-setup.md +1081 -0
  148. package/plugins/specweave-testing/commands/test-coverage.md +979 -0
  149. package/plugins/specweave-testing/commands/test-generate.md +1156 -0
  150. package/plugins/specweave-testing/commands/test-init.md +409 -0
  151. package/plugins/specweave-testing/skills/e2e-playwright/SKILL.md +769 -0
  152. package/plugins/specweave-testing/skills/tdd-expert/SKILL.md +934 -0
  153. package/plugins/specweave-testing/skills/unit-testing-expert/SKILL.md +1011 -0
  154. package/plugins/specweave-tooling/.claude-plugin/plugin.json +22 -0
  155. package/plugins/specweave-tooling/commands/specweave-tooling-skill-create.md +691 -0
  156. package/plugins/specweave-tooling/commands/specweave-tooling-skill-package.md +751 -0
  157. package/plugins/specweave-tooling/commands/specweave-tooling-skill-validate.md +858 -0
  158. package/plugins/specweave-ui/.claude-plugin/plugin.json +10 -0
  159. package/plugins/specweave-ui/commands/ui-automate.md +199 -0
  160. package/plugins/specweave-ui/commands/ui-inspect.md +70 -0
  161. package/plugins/specweave-ui/skills/browser-automation/SKILL.md +314 -0
  162. package/plugins/specweave-ui/skills/ui-testing/SKILL.md +716 -0
  163. package/plugins/specweave-ui/skills/visual-regression/SKILL.md +728 -0
  164. package/plugins/specweave/commands/check-hooks.md +0 -257
  165. package/plugins/specweave/commands/specweave-archive-increments.md +0 -82
  166. package/plugins/specweave-plugin-dev/skills/plugin-expert/SKILL.md +0 -1231
  167. /package/plugins/specweave/{agents/code-reviewer.md → skills/code-reviewer/SKILL.md} +0 -0
@@ -0,0 +1,1011 @@
1
+ ---
2
+ name: unit-testing-expert
3
+ description: Comprehensive unit testing expertise covering Vitest, Jest, test-driven development (TDD), mocking strategies, test coverage, snapshot testing, test architecture, testing patterns, dependency injection, test doubles (mocks, stubs, spies, fakes), async testing, error handling tests, parametric testing, test organization, code coverage analysis, mutation testing, and production-grade unit testing best practices. Activates for unit testing, vitest, jest, test-driven development, TDD, red-green-refactor, mocking, stubbing, spying, test doubles, test coverage, snapshot testing, test architecture, dependency injection, async testing, test patterns, code coverage, mutation testing, test isolation, test fixtures, AAA pattern, given-when-then, test organization, testing best practices, vi.fn, vi.mock, vi.spyOn, describe, it, expect, beforeEach, afterEach.
4
+ ---
5
+
6
+ # Unit Testing Expert
7
+
8
+ ## Core Expertise
9
+
10
+ ### 1. Vitest Fundamentals
11
+ **Modern Testing Framework** (Vite-native, Jest-compatible)
12
+
13
+ ```typescript
14
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
15
+ import { UserService } from './UserService';
16
+
17
+ describe('UserService', () => {
18
+ let userService: UserService;
19
+
20
+ beforeEach(() => {
21
+ userService = new UserService();
22
+ });
23
+
24
+ afterEach(() => {
25
+ vi.clearAllMocks();
26
+ });
27
+
28
+ it('should create a new user', () => {
29
+ const user = userService.create({ name: 'John', email: 'john@example.com' });
30
+
31
+ expect(user).toMatchObject({
32
+ id: expect.any(String),
33
+ name: 'John',
34
+ email: 'john@example.com',
35
+ createdAt: expect.any(Date),
36
+ });
37
+ });
38
+
39
+ it('should throw error for invalid email', () => {
40
+ expect(() => {
41
+ userService.create({ name: 'John', email: 'invalid' });
42
+ }).toThrow('Invalid email format');
43
+ });
44
+ });
45
+ ```
46
+
47
+ ### 2. Test-Driven Development (TDD)
48
+ **Red-Green-Refactor Cycle**
49
+
50
+ ```typescript
51
+ // RED: Write failing test first
52
+ describe('Calculator', () => {
53
+ it('should add two numbers', () => {
54
+ const calculator = new Calculator();
55
+ expect(calculator.add(2, 3)).toBe(5);
56
+ });
57
+ });
58
+
59
+ // GREEN: Implement minimal code to pass
60
+ class Calculator {
61
+ add(a: number, b: number): number {
62
+ return a + b;
63
+ }
64
+ }
65
+
66
+ // REFACTOR: Improve without breaking tests
67
+ class Calculator {
68
+ add(...numbers: number[]): number {
69
+ return numbers.reduce((sum, num) => sum + num, 0);
70
+ }
71
+ }
72
+
73
+ // Verify tests still pass
74
+ it('should add multiple numbers', () => {
75
+ const calculator = new Calculator();
76
+ expect(calculator.add(1, 2, 3, 4)).toBe(10);
77
+ });
78
+ ```
79
+
80
+ **TDD Benefits**:
81
+ - Forces modular, testable design
82
+ - Prevents over-engineering
83
+ - Living documentation
84
+ - Confident refactoring
85
+ - Faster debugging
86
+
87
+ ### 3. AAA Pattern (Arrange-Act-Assert)
88
+ **Structure for Clear Tests**
89
+
90
+ ```typescript
91
+ describe('OrderService', () => {
92
+ it('should calculate total with discount', () => {
93
+ // ARRANGE: Set up test data and dependencies
94
+ const orderService = new OrderService();
95
+ const order = {
96
+ items: [
97
+ { price: 100, quantity: 2 },
98
+ { price: 50, quantity: 1 },
99
+ ],
100
+ discountCode: 'SAVE20',
101
+ };
102
+
103
+ // ACT: Execute the behavior under test
104
+ const total = orderService.calculateTotal(order);
105
+
106
+ // ASSERT: Verify the result
107
+ expect(total).toBe(200); // (100*2 + 50*1) * 0.8 = 200
108
+ });
109
+ });
110
+ ```
111
+
112
+ **Alternative: Given-When-Then (BDD Style)**
113
+
114
+ ```typescript
115
+ describe('OrderService', () => {
116
+ it('should apply discount when valid code is provided', () => {
117
+ // GIVEN: An order with items and a discount code
118
+ const orderService = new OrderService();
119
+ const order = createOrder({ discountCode: 'SAVE20' });
120
+
121
+ // WHEN: Calculating the total
122
+ const total = orderService.calculateTotal(order);
123
+
124
+ // THEN: The discount should be applied
125
+ expect(total).toBe(200);
126
+ });
127
+ });
128
+ ```
129
+
130
+ ### 4. Mocking Strategies
131
+ **Test Doubles: Mocks, Stubs, Spies, Fakes**
132
+
133
+ #### Mocks (Track Calls + Control Behavior)
134
+ ```typescript
135
+ import { vi } from 'vitest';
136
+
137
+ describe('EmailService', () => {
138
+ it('should send welcome email on user registration', async () => {
139
+ // Mock external email API
140
+ const mockSendEmail = vi.fn().mockResolvedValue({ success: true });
141
+ const emailService = new EmailService({ sendEmail: mockSendEmail });
142
+
143
+ await emailService.sendWelcomeEmail('user@example.com');
144
+
145
+ // Verify mock was called correctly
146
+ expect(mockSendEmail).toHaveBeenCalledTimes(1);
147
+ expect(mockSendEmail).toHaveBeenCalledWith({
148
+ to: 'user@example.com',
149
+ subject: 'Welcome!',
150
+ body: expect.stringContaining('Welcome to our platform'),
151
+ });
152
+ });
153
+ });
154
+ ```
155
+
156
+ #### Spies (Track Calls on Real Methods)
157
+ ```typescript
158
+ describe('Logger', () => {
159
+ it('should log errors to console', () => {
160
+ const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
161
+
162
+ const logger = new Logger();
163
+ logger.error('Something went wrong');
164
+
165
+ expect(consoleErrorSpy).toHaveBeenCalledWith('[ERROR]', 'Something went wrong');
166
+
167
+ consoleErrorSpy.mockRestore();
168
+ });
169
+ });
170
+ ```
171
+
172
+ #### Stubs (Return Predefined Values)
173
+ ```typescript
174
+ describe('UserRepository', () => {
175
+ it('should fetch user by id', async () => {
176
+ // Stub database query
177
+ const dbStub = {
178
+ query: vi.fn().mockResolvedValue({
179
+ rows: [{ id: 1, name: 'John' }],
180
+ }),
181
+ };
182
+
183
+ const repo = new UserRepository(dbStub);
184
+ const user = await repo.findById(1);
185
+
186
+ expect(user).toEqual({ id: 1, name: 'John' });
187
+ });
188
+ });
189
+ ```
190
+
191
+ #### Fakes (Working Implementations for Testing)
192
+ ```typescript
193
+ // Fake in-memory database
194
+ class FakeDatabase {
195
+ private data: Map<string, any> = new Map();
196
+
197
+ async save(key: string, value: any): Promise<void> {
198
+ this.data.set(key, value);
199
+ }
200
+
201
+ async find(key: string): Promise<any> {
202
+ return this.data.get(key);
203
+ }
204
+
205
+ async delete(key: string): Promise<void> {
206
+ this.data.delete(key);
207
+ }
208
+ }
209
+
210
+ describe('CacheService', () => {
211
+ it('should store and retrieve values', async () => {
212
+ const fakeDb = new FakeDatabase();
213
+ const cache = new CacheService(fakeDb);
214
+
215
+ await cache.set('key', 'value');
216
+ const result = await cache.get('key');
217
+
218
+ expect(result).toBe('value');
219
+ });
220
+ });
221
+ ```
222
+
223
+ ### 5. Module Mocking
224
+ **Mock Entire Modules**
225
+
226
+ ```typescript
227
+ // Mock external dependency
228
+ vi.mock('./database', () => ({
229
+ Database: vi.fn().mockImplementation(() => ({
230
+ connect: vi.fn().mockResolvedValue(true),
231
+ query: vi.fn().mockResolvedValue({ rows: [] }),
232
+ disconnect: vi.fn(),
233
+ })),
234
+ }));
235
+
236
+ import { Database } from './database';
237
+ import { UserService } from './UserService';
238
+
239
+ describe('UserService', () => {
240
+ it('should connect to database on initialization', async () => {
241
+ const userService = new UserService();
242
+ await userService.init();
243
+
244
+ expect(Database).toHaveBeenCalledTimes(1);
245
+ });
246
+ });
247
+ ```
248
+
249
+ **Partial Module Mocking**
250
+
251
+ ```typescript
252
+ vi.mock('./utils', async (importOriginal) => {
253
+ const actual = await importOriginal();
254
+ return {
255
+ ...actual,
256
+ // Mock only specific functions
257
+ fetchData: vi.fn().mockResolvedValue({ data: 'mocked' }),
258
+ };
259
+ });
260
+ ```
261
+
262
+ **Auto-mocking with vi.hoisted**
263
+
264
+ ```typescript
265
+ import { vi } from 'vitest';
266
+
267
+ // Hoist mocks to top (before imports)
268
+ vi.hoisted(() => {
269
+ vi.mock('./config', () => ({
270
+ API_URL: 'https://test-api.example.com',
271
+ }));
272
+ });
273
+
274
+ import { API_URL } from './config';
275
+ ```
276
+
277
+ ### 6. Async Testing
278
+ **Handle Promises, Timers, and Callbacks**
279
+
280
+ #### Testing Promises
281
+ ```typescript
282
+ describe('AsyncService', () => {
283
+ it('should resolve with data', async () => {
284
+ const service = new AsyncService();
285
+
286
+ const result = await service.fetchData();
287
+
288
+ expect(result).toEqual({ id: 1, name: 'Test' });
289
+ });
290
+
291
+ it('should reject with error', async () => {
292
+ const service = new AsyncService();
293
+
294
+ await expect(service.fetchInvalidData()).rejects.toThrow('Not found');
295
+ });
296
+
297
+ it('should handle multiple async operations', async () => {
298
+ const service = new AsyncService();
299
+
300
+ const [user, posts] = await Promise.all([
301
+ service.fetchUser(1),
302
+ service.fetchPosts(1),
303
+ ]);
304
+
305
+ expect(user.id).toBe(1);
306
+ expect(posts.length).toBeGreaterThan(0);
307
+ });
308
+ });
309
+ ```
310
+
311
+ #### Testing Timers
312
+ ```typescript
313
+ describe('DebounceService', () => {
314
+ beforeEach(() => {
315
+ vi.useFakeTimers();
316
+ });
317
+
318
+ afterEach(() => {
319
+ vi.restoreAllTimers();
320
+ });
321
+
322
+ it('should debounce function calls', () => {
323
+ const callback = vi.fn();
324
+ const debounced = debounce(callback, 1000);
325
+
326
+ debounced();
327
+ debounced();
328
+ debounced();
329
+
330
+ expect(callback).not.toHaveBeenCalled();
331
+
332
+ // Fast-forward time
333
+ vi.advanceTimersByTime(1000);
334
+
335
+ expect(callback).toHaveBeenCalledTimes(1);
336
+ });
337
+
338
+ it('should cancel pending debounced calls', () => {
339
+ const callback = vi.fn();
340
+ const debounced = debounce(callback, 1000);
341
+
342
+ debounced();
343
+ debounced.cancel();
344
+
345
+ vi.advanceTimersByTime(1000);
346
+
347
+ expect(callback).not.toHaveBeenCalled();
348
+ });
349
+ });
350
+ ```
351
+
352
+ #### Testing Callbacks
353
+ ```typescript
354
+ describe('EventEmitter', () => {
355
+ it('should execute callback on event', (done) => {
356
+ const emitter = new EventEmitter();
357
+
358
+ emitter.on('data', (data) => {
359
+ expect(data).toBe('test');
360
+ done(); // Signal async completion
361
+ });
362
+
363
+ emitter.emit('data', 'test');
364
+ });
365
+
366
+ // Modern alternative: Promisify
367
+ it('should execute callback on event (promisified)', () => {
368
+ const emitter = new EventEmitter();
369
+
370
+ const promise = new Promise((resolve) => {
371
+ emitter.on('data', resolve);
372
+ });
373
+
374
+ emitter.emit('data', 'test');
375
+
376
+ return expect(promise).resolves.toBe('test');
377
+ });
378
+ });
379
+ ```
380
+
381
+ ### 7. Parametric Testing (Table-Driven Tests)
382
+ **Test Multiple Cases Efficiently**
383
+
384
+ ```typescript
385
+ describe.each([
386
+ { input: 2, expected: 4 },
387
+ { input: 3, expected: 9 },
388
+ { input: 4, expected: 16 },
389
+ { input: 5, expected: 25 },
390
+ ])('square($input)', ({ input, expected }) => {
391
+ it(`should return ${expected}`, () => {
392
+ expect(square(input)).toBe(expected);
393
+ });
394
+ });
395
+
396
+ // Alternative syntax
397
+ it.each([
398
+ [1, 2, 3],
399
+ [2, 3, 5],
400
+ [3, 4, 7],
401
+ ])('add(%i, %i) should equal %i', (a, b, expected) => {
402
+ expect(add(a, b)).toBe(expected);
403
+ });
404
+
405
+ // Complex validation
406
+ describe.each([
407
+ { email: 'user@example.com', valid: true },
408
+ { email: 'invalid', valid: false },
409
+ { email: 'missing@', valid: false },
410
+ { email: '@domain.com', valid: false },
411
+ { email: '', valid: false },
412
+ ])('validateEmail($email)', ({ email, valid }) => {
413
+ it(`should return ${valid}`, () => {
414
+ expect(validateEmail(email)).toBe(valid);
415
+ });
416
+ });
417
+ ```
418
+
419
+ ### 8. Snapshot Testing
420
+ **Capture and Compare Complex Outputs**
421
+
422
+ ```typescript
423
+ describe('ComponentRenderer', () => {
424
+ it('should render user profile correctly', () => {
425
+ const user = { id: 1, name: 'John', email: 'john@example.com' };
426
+ const rendered = renderUserProfile(user);
427
+
428
+ expect(rendered).toMatchSnapshot();
429
+ });
430
+
431
+ it('should render empty state', () => {
432
+ const rendered = renderUserProfile(null);
433
+
434
+ expect(rendered).toMatchSnapshot();
435
+ });
436
+
437
+ // Inline snapshots (better for small outputs)
438
+ it('should format date', () => {
439
+ const formatted = formatDate(new Date('2025-01-15'));
440
+
441
+ expect(formatted).toMatchInlineSnapshot('"January 15, 2025"');
442
+ });
443
+ });
444
+ ```
445
+
446
+ **Update snapshots**: `npm test -- -u`
447
+
448
+ **Snapshot Best Practices**:
449
+ - Use for UI components, API responses, complex objects
450
+ - Keep snapshots small and focused
451
+ - Review snapshot diffs carefully in PRs
452
+ - Avoid snapshots for simple values (use `.toBe()` instead)
453
+
454
+ ### 9. Error Handling Tests
455
+ **Verify Error Conditions**
456
+
457
+ ```typescript
458
+ describe('ValidationService', () => {
459
+ it('should throw for invalid input', () => {
460
+ const validator = new ValidationService();
461
+
462
+ expect(() => {
463
+ validator.validate(null);
464
+ }).toThrow('Input is required');
465
+ });
466
+
467
+ it('should throw specific error type', () => {
468
+ const validator = new ValidationService();
469
+
470
+ expect(() => {
471
+ validator.validate({ age: -1 });
472
+ }).toThrow(ValidationError);
473
+ });
474
+
475
+ it('should include error details', () => {
476
+ const validator = new ValidationService();
477
+
478
+ try {
479
+ validator.validate({ age: 'invalid' });
480
+ fail('Expected error to be thrown');
481
+ } catch (error) {
482
+ expect(error).toBeInstanceOf(ValidationError);
483
+ expect(error.message).toBe('Age must be a number');
484
+ expect(error.field).toBe('age');
485
+ expect(error.code).toBe('INVALID_TYPE');
486
+ }
487
+ });
488
+
489
+ it('should handle async errors', async () => {
490
+ const service = new AsyncService();
491
+
492
+ await expect(async () => {
493
+ await service.fetchWithInvalidToken();
494
+ }).rejects.toThrow('Unauthorized');
495
+ });
496
+ });
497
+ ```
498
+
499
+ ### 10. Dependency Injection for Testability
500
+ **Design for Easy Testing**
501
+
502
+ ```typescript
503
+ // ❌ BAD: Hard to test (tight coupling)
504
+ class UserService {
505
+ async getUser(id: string) {
506
+ const db = new Database(); // Hard-coded dependency
507
+ return db.query('SELECT * FROM users WHERE id = ?', [id]);
508
+ }
509
+ }
510
+
511
+ // ✅ GOOD: Easy to test (dependency injection)
512
+ class UserService {
513
+ constructor(private db: Database) {}
514
+
515
+ async getUser(id: string) {
516
+ return this.db.query('SELECT * FROM users WHERE id = ?', [id]);
517
+ }
518
+ }
519
+
520
+ // Test with mock
521
+ describe('UserService', () => {
522
+ it('should fetch user by id', async () => {
523
+ const mockDb = {
524
+ query: vi.fn().mockResolvedValue({ id: '1', name: 'John' }),
525
+ };
526
+
527
+ const service = new UserService(mockDb as any);
528
+ const user = await service.getUser('1');
529
+
530
+ expect(user).toEqual({ id: '1', name: 'John' });
531
+ expect(mockDb.query).toHaveBeenCalledWith(
532
+ 'SELECT * FROM users WHERE id = ?',
533
+ ['1']
534
+ );
535
+ });
536
+ });
537
+ ```
538
+
539
+ **Factory Pattern for Dependencies**
540
+
541
+ ```typescript
542
+ interface Dependencies {
543
+ logger?: Logger;
544
+ cache?: Cache;
545
+ db?: Database;
546
+ }
547
+
548
+ class UserService {
549
+ private logger: Logger;
550
+ private cache: Cache;
551
+ private db: Database;
552
+
553
+ constructor(deps: Dependencies = {}) {
554
+ this.logger = deps.logger ?? new ConsoleLogger();
555
+ this.cache = deps.cache ?? new RedisCache();
556
+ this.db = deps.db ?? new PostgresDatabase();
557
+ }
558
+ }
559
+
560
+ // Production
561
+ const service = new UserService();
562
+
563
+ // Testing
564
+ const service = new UserService({
565
+ logger: silentLogger,
566
+ cache: inMemoryCache,
567
+ db: mockDatabase,
568
+ });
569
+ ```
570
+
571
+ ### 11. Test Coverage Analysis
572
+ **Measure and Improve Coverage**
573
+
574
+ ```typescript
575
+ // vitest.config.ts
576
+ export default defineConfig({
577
+ test: {
578
+ coverage: {
579
+ provider: 'v8', // or 'istanbul'
580
+ reporter: ['text', 'json', 'html', 'lcov'],
581
+ include: ['src/**/*.ts'],
582
+ exclude: [
583
+ 'src/**/*.test.ts',
584
+ 'src/**/*.spec.ts',
585
+ 'src/types/**',
586
+ 'src/index.ts',
587
+ ],
588
+ thresholds: {
589
+ statements: 80,
590
+ branches: 75,
591
+ functions: 80,
592
+ lines: 80,
593
+ },
594
+ },
595
+ },
596
+ });
597
+ ```
598
+
599
+ **Run with coverage**: `npm test -- --coverage`
600
+
601
+ **Coverage Types**:
602
+ - **Statement**: % of code statements executed
603
+ - **Branch**: % of conditional branches tested
604
+ - **Function**: % of functions called
605
+ - **Line**: % of lines executed
606
+
607
+ **Coverage Best Practices**:
608
+ - Aim for 80%+ overall coverage
609
+ - 100% coverage ≠ bug-free (test quality matters)
610
+ - Focus on critical paths and edge cases
611
+ - Use coverage to find untested code
612
+ - Don't game the system (write meaningful tests)
613
+
614
+ ### 12. Test Organization
615
+ **Structure for Maintainability**
616
+
617
+ ```
618
+ src/
619
+ ├── services/
620
+ │ ├── UserService.ts
621
+ │ ├── UserService.test.ts # Co-located tests
622
+ │ ├── OrderService.ts
623
+ │ └── OrderService.test.ts
624
+ ├── utils/
625
+ │ ├── validation.ts
626
+ │ ├── validation.test.ts
627
+ │ ├── formatting.ts
628
+ │ └── formatting.test.ts
629
+ └── __tests__/ # Alternative: separate test dir
630
+ ├── unit/
631
+ │ ├── services/
632
+ │ │ ├── UserService.test.ts
633
+ │ │ └── OrderService.test.ts
634
+ │ └── utils/
635
+ │ ├── validation.test.ts
636
+ │ └── formatting.test.ts
637
+ └── integration/
638
+ ├── api.test.ts
639
+ └── database.test.ts
640
+ ```
641
+
642
+ **Naming Conventions**:
643
+ - Test files: `*.test.ts` or `*.spec.ts`
644
+ - Test suites: `describe('ClassName')`
645
+ - Test cases: `it('should do something specific')`
646
+ - Helper files: `*.fixture.ts`, `*.mock.ts`
647
+
648
+ ### 13. Test Fixtures & Helpers
649
+ **Reusable Test Data and Utilities**
650
+
651
+ ```typescript
652
+ // fixtures/user.fixture.ts
653
+ export const createUser = (overrides = {}) => ({
654
+ id: '1',
655
+ name: 'John Doe',
656
+ email: 'john@example.com',
657
+ createdAt: new Date('2025-01-01'),
658
+ ...overrides,
659
+ });
660
+
661
+ export const createUserList = (count = 3) =>
662
+ Array.from({ length: count }, (_, i) =>
663
+ createUser({ id: String(i + 1), name: `User ${i + 1}` })
664
+ );
665
+
666
+ // Usage in tests
667
+ import { createUser, createUserList } from '../fixtures/user.fixture';
668
+
669
+ describe('UserRepository', () => {
670
+ it('should save user', async () => {
671
+ const user = createUser({ name: 'Jane' });
672
+ await repo.save(user);
673
+
674
+ expect(await repo.findById(user.id)).toEqual(user);
675
+ });
676
+
677
+ it('should find all users', async () => {
678
+ const users = createUserList(5);
679
+ await Promise.all(users.map(u => repo.save(u)));
680
+
681
+ expect(await repo.findAll()).toHaveLength(5);
682
+ });
683
+ });
684
+ ```
685
+
686
+ **Test Helpers**
687
+
688
+ ```typescript
689
+ // helpers/test-utils.ts
690
+ export const waitFor = (condition: () => boolean, timeout = 1000) => {
691
+ return new Promise((resolve, reject) => {
692
+ const startTime = Date.now();
693
+ const interval = setInterval(() => {
694
+ if (condition()) {
695
+ clearInterval(interval);
696
+ resolve(true);
697
+ } else if (Date.now() - startTime > timeout) {
698
+ clearInterval(interval);
699
+ reject(new Error('Timeout waiting for condition'));
700
+ }
701
+ }, 10);
702
+ });
703
+ };
704
+
705
+ export const flushPromises = () => new Promise(resolve => setImmediate(resolve));
706
+
707
+ export const createMockLogger = () => ({
708
+ info: vi.fn(),
709
+ warn: vi.fn(),
710
+ error: vi.fn(),
711
+ debug: vi.fn(),
712
+ });
713
+ ```
714
+
715
+ ### 14. Advanced Matchers
716
+ **Custom and Built-in Matchers**
717
+
718
+ ```typescript
719
+ describe('Advanced Matchers', () => {
720
+ // Equality
721
+ it('exact equality', () => expect(1 + 1).toBe(2));
722
+ it('deep equality', () => expect({ a: 1 }).toEqual({ a: 1 }));
723
+ it('reference equality', () => {
724
+ const obj = { a: 1 };
725
+ expect(obj).toBe(obj);
726
+ });
727
+
728
+ // Truthiness
729
+ it('truthy', () => expect(true).toBeTruthy());
730
+ it('falsy', () => expect(false).toBeFalsy());
731
+ it('defined', () => expect('value').toBeDefined());
732
+ it('undefined', () => expect(undefined).toBeUndefined());
733
+ it('null', () => expect(null).toBeNull());
734
+
735
+ // Numbers
736
+ it('greater than', () => expect(10).toBeGreaterThan(5));
737
+ it('less than', () => expect(5).toBeLessThan(10));
738
+ it('close to', () => expect(0.1 + 0.2).toBeCloseTo(0.3, 5));
739
+
740
+ // Strings
741
+ it('contains', () => expect('hello world').toContain('world'));
742
+ it('matches regex', () => expect('test@example.com').toMatch(/^\S+@\S+$/));
743
+
744
+ // Arrays
745
+ it('contains item', () => expect([1, 2, 3]).toContain(2));
746
+ it('has length', () => expect([1, 2, 3]).toHaveLength(3));
747
+ it('contains object', () => {
748
+ expect([{ id: 1 }, { id: 2 }]).toContainEqual({ id: 1 });
749
+ });
750
+
751
+ // Objects
752
+ it('matches object', () => {
753
+ expect({ id: 1, name: 'John', age: 30 }).toMatchObject({
754
+ id: 1,
755
+ name: 'John',
756
+ });
757
+ });
758
+ it('has property', () => expect({ a: 1 }).toHaveProperty('a'));
759
+ it('has property with value', () => expect({ a: 1 }).toHaveProperty('a', 1));
760
+
761
+ // Functions
762
+ it('throws', () => {
763
+ expect(() => { throw new Error('fail'); }).toThrow('fail');
764
+ });
765
+ it('called', () => {
766
+ const mock = vi.fn();
767
+ mock();
768
+ expect(mock).toHaveBeenCalled();
769
+ });
770
+ it('called with', () => {
771
+ const mock = vi.fn();
772
+ mock(1, 2);
773
+ expect(mock).toHaveBeenCalledWith(1, 2);
774
+ });
775
+
776
+ // Negation
777
+ it('not equal', () => expect(1).not.toBe(2));
778
+ });
779
+ ```
780
+
781
+ **Custom Matchers**
782
+
783
+ ```typescript
784
+ import { expect } from 'vitest';
785
+
786
+ expect.extend({
787
+ toBeValidEmail(received: string) {
788
+ const pass = /^\S+@\S+\.\S+$/.test(received);
789
+ return {
790
+ pass,
791
+ message: () =>
792
+ pass
793
+ ? `expected ${received} not to be a valid email`
794
+ : `expected ${received} to be a valid email`,
795
+ };
796
+ },
797
+ });
798
+
799
+ // Usage
800
+ it('should validate email', () => {
801
+ expect('user@example.com').toBeValidEmail();
802
+ expect('invalid').not.toBeValidEmail();
803
+ });
804
+ ```
805
+
806
+ ### 15. Test Lifecycle Hooks
807
+ **Setup and Teardown**
808
+
809
+ ```typescript
810
+ describe('Database Tests', () => {
811
+ let db: Database;
812
+
813
+ // Run once before all tests in suite
814
+ beforeAll(async () => {
815
+ db = new Database();
816
+ await db.connect();
817
+ });
818
+
819
+ // Run once after all tests in suite
820
+ afterAll(async () => {
821
+ await db.disconnect();
822
+ });
823
+
824
+ // Run before each test
825
+ beforeEach(async () => {
826
+ await db.clear();
827
+ await db.seed();
828
+ });
829
+
830
+ // Run after each test
831
+ afterEach(async () => {
832
+ await db.rollback();
833
+ });
834
+
835
+ it('should insert user', async () => {
836
+ await db.insert('users', { name: 'John' });
837
+ const users = await db.query('users');
838
+ expect(users).toHaveLength(1);
839
+ });
840
+
841
+ it('should delete user', async () => {
842
+ await db.insert('users', { name: 'John' });
843
+ await db.delete('users', { name: 'John' });
844
+ const users = await db.query('users');
845
+ expect(users).toHaveLength(0);
846
+ });
847
+ });
848
+ ```
849
+
850
+ **Conditional Execution**
851
+
852
+ ```typescript
853
+ // Skip tests
854
+ it.skip('not ready yet', () => {});
855
+ it.todo('implement later');
856
+
857
+ // Only run specific tests
858
+ it.only('focus on this test', () => {});
859
+
860
+ // Run if condition met
861
+ it.runIf(process.env.CI)('CI only test', () => {});
862
+
863
+ // Skip if condition met
864
+ it.skipIf(process.platform === 'win32')('Unix only test', () => {});
865
+
866
+ // Concurrent execution
867
+ describe.concurrent('parallel tests', () => {
868
+ it('test 1', async () => { /* runs in parallel */ });
869
+ it('test 2', async () => { /* runs in parallel */ });
870
+ });
871
+ ```
872
+
873
+ ## Best Practices
874
+
875
+ ### Test Isolation
876
+ - Each test should be independent
877
+ - No shared state between tests
878
+ - Use `beforeEach` to reset state
879
+ - Avoid global variables
880
+ - Clean up resources in `afterEach`
881
+
882
+ ### Test Naming
883
+ - Use descriptive names: `it('should return user when id exists')`
884
+ - Follow "should" convention
885
+ - Be specific about what is being tested
886
+ - Include edge cases in name: `it('should handle empty array')`
887
+
888
+ ### Avoid Test Smells
889
+ ❌ **Don't**:
890
+ - Test implementation details
891
+ - Write slow tests (mock external deps)
892
+ - Use magic numbers (use constants)
893
+ - Share state between tests
894
+ - Test framework code (test YOUR code)
895
+
896
+ ✅ **Do**:
897
+ - Test behavior, not implementation
898
+ - Keep tests fast (< 100ms per test)
899
+ - Use descriptive variable names
900
+ - Isolate tests completely
901
+ - Focus on edge cases and error paths
902
+
903
+ ### Performance
904
+ - Mock expensive operations (DB, API, file I/O)
905
+ - Use fake timers for time-based code
906
+ - Run tests in parallel (`--threads`)
907
+ - Cache test fixtures
908
+ - Profile slow tests: `npm test -- --reporter=verbose`
909
+
910
+ ## Common Patterns
911
+
912
+ ### Testing Classes
913
+ ```typescript
914
+ class Counter {
915
+ private count = 0;
916
+
917
+ increment() { this.count++; }
918
+ decrement() { this.count--; }
919
+ getValue() { return this.count; }
920
+ }
921
+
922
+ describe('Counter', () => {
923
+ let counter: Counter;
924
+
925
+ beforeEach(() => {
926
+ counter = new Counter();
927
+ });
928
+
929
+ it('should start at 0', () => {
930
+ expect(counter.getValue()).toBe(0);
931
+ });
932
+
933
+ it('should increment', () => {
934
+ counter.increment();
935
+ expect(counter.getValue()).toBe(1);
936
+ });
937
+
938
+ it('should decrement', () => {
939
+ counter.decrement();
940
+ expect(counter.getValue()).toBe(-1);
941
+ });
942
+ });
943
+ ```
944
+
945
+ ### Testing Utilities
946
+ ```typescript
947
+ describe('formatCurrency', () => {
948
+ it.each([
949
+ [1000, '$1,000.00'],
950
+ [0.5, '$0.50'],
951
+ [-100, '-$100.00'],
952
+ ])('formatCurrency(%i) should return %s', (input, expected) => {
953
+ expect(formatCurrency(input)).toBe(expected);
954
+ });
955
+ });
956
+ ```
957
+
958
+ ### Testing Hooks (React, Vue)
959
+ ```typescript
960
+ import { renderHook, waitFor } from '@testing-library/react';
961
+ import { useCounter } from './useCounter';
962
+
963
+ describe('useCounter', () => {
964
+ it('should increment', () => {
965
+ const { result } = renderHook(() => useCounter());
966
+
967
+ act(() => {
968
+ result.current.increment();
969
+ });
970
+
971
+ expect(result.current.count).toBe(1);
972
+ });
973
+ });
974
+ ```
975
+
976
+ ## Troubleshooting
977
+
978
+ ### Common Issues
979
+ 1. **Timeouts**: Increase timeout for slow async operations
980
+ 2. **Flaky tests**: Ensure proper cleanup, avoid race conditions
981
+ 3. **Mock not working**: Check mock is hoisted, correct path
982
+ 4. **Coverage gaps**: Use `--coverage` to identify untested code
983
+ 5. **Slow tests**: Profile and mock expensive operations
984
+
985
+ ### Debug Strategies
986
+ ```bash
987
+ # Run single test
988
+ npm test -- path/to/test.ts
989
+
990
+ # Run tests matching pattern
991
+ npm test -- --grep "UserService"
992
+
993
+ # Debug mode (Node inspector)
994
+ node --inspect-brk node_modules/.bin/vitest run
995
+
996
+ # Watch mode
997
+ npm test -- --watch
998
+
999
+ # Verbose output
1000
+ npm test -- --reporter=verbose
1001
+
1002
+ # Coverage report
1003
+ npm test -- --coverage
1004
+ ```
1005
+
1006
+ ## Resources
1007
+ - **Vitest Docs**: https://vitest.dev
1008
+ - **Testing Library**: https://testing-library.com
1009
+ - **Jest API**: https://jestjs.io/docs/api (Jest-compatible)
1010
+ - **TDD Guide**: https://martinfowler.com/bliki/TestDrivenDevelopment.html
1011
+ - **Test Doubles**: https://martinfowler.com/bliki/TestDouble.html