omgkit 2.1.0 → 2.2.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 (56) hide show
  1. package/package.json +1 -1
  2. package/plugin/skills/SKILL_STANDARDS.md +743 -0
  3. package/plugin/skills/databases/mongodb/SKILL.md +797 -28
  4. package/plugin/skills/databases/postgresql/SKILL.md +494 -18
  5. package/plugin/skills/databases/prisma/SKILL.md +776 -30
  6. package/plugin/skills/databases/redis/SKILL.md +885 -25
  7. package/plugin/skills/devops/aws/SKILL.md +686 -28
  8. package/plugin/skills/devops/docker/SKILL.md +466 -18
  9. package/plugin/skills/devops/github-actions/SKILL.md +684 -29
  10. package/plugin/skills/devops/kubernetes/SKILL.md +621 -24
  11. package/plugin/skills/frameworks/django/SKILL.md +920 -20
  12. package/plugin/skills/frameworks/express/SKILL.md +1361 -35
  13. package/plugin/skills/frameworks/fastapi/SKILL.md +1260 -33
  14. package/plugin/skills/frameworks/laravel/SKILL.md +1244 -31
  15. package/plugin/skills/frameworks/nestjs/SKILL.md +1005 -26
  16. package/plugin/skills/frameworks/nextjs/SKILL.md +407 -44
  17. package/plugin/skills/frameworks/rails/SKILL.md +594 -28
  18. package/plugin/skills/frameworks/react/SKILL.md +1006 -32
  19. package/plugin/skills/frameworks/spring/SKILL.md +528 -35
  20. package/plugin/skills/frameworks/vue/SKILL.md +1296 -27
  21. package/plugin/skills/frontend/accessibility/SKILL.md +1108 -34
  22. package/plugin/skills/frontend/frontend-design/SKILL.md +1304 -26
  23. package/plugin/skills/frontend/responsive/SKILL.md +847 -21
  24. package/plugin/skills/frontend/shadcn-ui/SKILL.md +976 -38
  25. package/plugin/skills/frontend/tailwindcss/SKILL.md +831 -35
  26. package/plugin/skills/frontend/threejs/SKILL.md +1298 -29
  27. package/plugin/skills/languages/javascript/SKILL.md +935 -31
  28. package/plugin/skills/languages/python/SKILL.md +489 -25
  29. package/plugin/skills/languages/typescript/SKILL.md +379 -30
  30. package/plugin/skills/methodology/brainstorming/SKILL.md +597 -23
  31. package/plugin/skills/methodology/defense-in-depth/SKILL.md +832 -34
  32. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +665 -31
  33. package/plugin/skills/methodology/executing-plans/SKILL.md +556 -24
  34. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +595 -25
  35. package/plugin/skills/methodology/problem-solving/SKILL.md +429 -61
  36. package/plugin/skills/methodology/receiving-code-review/SKILL.md +536 -24
  37. package/plugin/skills/methodology/requesting-code-review/SKILL.md +632 -21
  38. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +641 -30
  39. package/plugin/skills/methodology/sequential-thinking/SKILL.md +262 -3
  40. package/plugin/skills/methodology/systematic-debugging/SKILL.md +571 -32
  41. package/plugin/skills/methodology/test-driven-development/SKILL.md +779 -24
  42. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +691 -29
  43. package/plugin/skills/methodology/token-optimization/SKILL.md +598 -29
  44. package/plugin/skills/methodology/verification-before-completion/SKILL.md +543 -22
  45. package/plugin/skills/methodology/writing-plans/SKILL.md +590 -18
  46. package/plugin/skills/omega/omega-architecture/SKILL.md +838 -39
  47. package/plugin/skills/omega/omega-coding/SKILL.md +636 -39
  48. package/plugin/skills/omega/omega-sprint/SKILL.md +855 -48
  49. package/plugin/skills/omega/omega-testing/SKILL.md +940 -41
  50. package/plugin/skills/omega/omega-thinking/SKILL.md +703 -50
  51. package/plugin/skills/security/better-auth/SKILL.md +1065 -28
  52. package/plugin/skills/security/oauth/SKILL.md +968 -31
  53. package/plugin/skills/security/owasp/SKILL.md +894 -33
  54. package/plugin/skills/testing/playwright/SKILL.md +764 -38
  55. package/plugin/skills/testing/pytest/SKILL.md +873 -36
  56. package/plugin/skills/testing/vitest/SKILL.md +980 -35
@@ -1,39 +1,794 @@
1
1
  ---
2
2
  name: test-driven-development
3
- description: TDD workflow. Use when implementing features with test-first approach.
3
+ description: Test-first development with Red-Green-Refactor cycle, behavior-driven specs, and comprehensive testing strategies
4
+ category: methodology
5
+ triggers:
6
+ - tdd
7
+ - test driven
8
+ - test first
9
+ - red green refactor
10
+ - write tests first
11
+ - testing methodology
4
12
  ---
5
13
 
6
- # Test-Driven Development Skill
14
+ # Test-Driven Development
7
15
 
8
- ## TDD Cycle
9
- 1. 🔴 **RED** - Write failing test
10
- 2. 🟢 **GREEN** - Make it pass (minimal code)
11
- 3. ♻️ **REFACTOR** - Improve code
16
+ Master **test-first development** with the Red-Green-Refactor cycle. This skill enables confident code changes through comprehensive test coverage, emergent design, and continuous validation.
12
17
 
13
- ## Rules
14
- - No production code without failing test
15
- - Only enough code to pass test
16
- - Refactor only when green
17
- - One assertion per test
18
+ ## Purpose
18
19
 
19
- ## Example
20
+ Build reliable software through test-first practices:
21
+
22
+ - Write failing tests before implementation
23
+ - Design interfaces through test usage
24
+ - Achieve comprehensive coverage naturally
25
+ - Enable fearless refactoring
26
+ - Document behavior through specs
27
+ - Catch regressions immediately
28
+ - Improve code design through testability
29
+
30
+ ## Features
31
+
32
+ ### 1. The Red-Green-Refactor Cycle
33
+
34
+ ```markdown
35
+ ## TDD Cycle Visualization
36
+
37
+ ┌───────────────────────────────────────────────────┐
38
+ │ │
39
+ │ 🔴 RED │
40
+ │ Write a failing test │
41
+ │ - Test doesn't compile OR │
42
+ │ - Test fails with assertion error │
43
+ │ │
44
+ │ │ │
45
+ │ ▼ │
46
+ │ │
47
+ │ 🟢 GREEN │
48
+ │ Make the test pass │
49
+ │ - Write MINIMAL code │
50
+ │ - No more than needed to pass │
51
+ │ - "Fake it till you make it" │
52
+ │ │
53
+ │ │ │
54
+ │ ▼ │
55
+ │ │
56
+ │ 🔄 REFACTOR │
57
+ │ Improve the code │
58
+ │ - Remove duplication │
59
+ │ - Improve naming │
60
+ │ - Extract methods/classes │
61
+ │ - Keep tests green! │
62
+ │ │
63
+ │ │ │
64
+ │ ▼ │
65
+ │ │
66
+ │ Next Test ──────────────────► 🔴 │
67
+ │ │
68
+ └───────────────────────────────────────────────────┘
69
+
70
+ ## Cycle Duration
71
+ - Ideal cycle: 1-5 minutes
72
+ - Maximum cycle: 10 minutes
73
+ - If stuck longer: Undo and try smaller step
74
+ ```
75
+
76
+ ### 2. Writing Effective Tests
77
+
78
+ ```typescript
79
+ // TDD Example: Building a Password Validator
80
+
81
+ // ═══════════════════════════════════════════════════════════
82
+ // ITERATION 1: Minimum length requirement
83
+ // ═══════════════════════════════════════════════════════════
84
+
85
+ // 🔴 RED: Write failing test first
86
+ describe('PasswordValidator', () => {
87
+ describe('validate', () => {
88
+ it('returns invalid for passwords shorter than 8 characters', () => {
89
+ const result = validatePassword('short');
90
+ expect(result.valid).toBe(false);
91
+ expect(result.errors).toContain('Password must be at least 8 characters');
92
+ });
93
+ });
94
+ });
95
+
96
+ // 🟢 GREEN: Minimal implementation to pass
97
+ interface ValidationResult {
98
+ valid: boolean;
99
+ errors: string[];
100
+ }
101
+
102
+ function validatePassword(password: string): ValidationResult {
103
+ const errors: string[] = [];
104
+
105
+ if (password.length < 8) {
106
+ errors.push('Password must be at least 8 characters');
107
+ }
108
+
109
+ return {
110
+ valid: errors.length === 0,
111
+ errors,
112
+ };
113
+ }
114
+
115
+ // 🔄 REFACTOR: Nothing to refactor yet
116
+
117
+ // ═══════════════════════════════════════════════════════════
118
+ // ITERATION 2: Valid password passes
119
+ // ═══════════════════════════════════════════════════════════
120
+
121
+ // 🔴 RED: Add test for valid case
122
+ it('returns valid for password with 8+ characters', () => {
123
+ const result = validatePassword('validpwd');
124
+ expect(result.valid).toBe(true);
125
+ expect(result.errors).toHaveLength(0);
126
+ });
127
+
128
+ // 🟢 GREEN: Already passes! No changes needed.
129
+
130
+ // ═══════════════════════════════════════════════════════════
131
+ // ITERATION 3: Require uppercase letter
132
+ // ═══════════════════════════════════════════════════════════
133
+
134
+ // 🔴 RED: Add uppercase requirement
135
+ it('returns invalid without uppercase letter', () => {
136
+ const result = validatePassword('lowercase123');
137
+ expect(result.valid).toBe(false);
138
+ expect(result.errors).toContain('Password must contain uppercase letter');
139
+ });
140
+
141
+ // 🟢 GREEN: Add uppercase check
142
+ function validatePassword(password: string): ValidationResult {
143
+ const errors: string[] = [];
144
+
145
+ if (password.length < 8) {
146
+ errors.push('Password must be at least 8 characters');
147
+ }
148
+
149
+ if (!/[A-Z]/.test(password)) {
150
+ errors.push('Password must contain uppercase letter');
151
+ }
152
+
153
+ return {
154
+ valid: errors.length === 0,
155
+ errors,
156
+ };
157
+ }
158
+
159
+ // 🔄 REFACTOR: Extract validation rules
160
+ interface ValidationRule {
161
+ test: (password: string) => boolean;
162
+ message: string;
163
+ }
164
+
165
+ const passwordRules: ValidationRule[] = [
166
+ {
167
+ test: (p) => p.length >= 8,
168
+ message: 'Password must be at least 8 characters',
169
+ },
170
+ {
171
+ test: (p) => /[A-Z]/.test(p),
172
+ message: 'Password must contain uppercase letter',
173
+ },
174
+ ];
175
+
176
+ function validatePassword(password: string): ValidationResult {
177
+ const errors = passwordRules
178
+ .filter((rule) => !rule.test(password))
179
+ .map((rule) => rule.message);
180
+
181
+ return {
182
+ valid: errors.length === 0,
183
+ errors,
184
+ };
185
+ }
186
+
187
+ // ═══════════════════════════════════════════════════════════
188
+ // ITERATION 4: More rules become easy to add!
189
+ // ═══════════════════════════════════════════════════════════
190
+
191
+ // 🔴 RED
192
+ it('returns invalid without number', () => {
193
+ const result = validatePassword('NoNumbers!');
194
+ expect(result.valid).toBe(false);
195
+ expect(result.errors).toContain('Password must contain a number');
196
+ });
197
+
198
+ // 🟢 GREEN: Just add to rules array
199
+ passwordRules.push({
200
+ test: (p) => /\d/.test(p),
201
+ message: 'Password must contain a number',
202
+ });
203
+ ```
204
+
205
+ ### 3. Test Patterns and Structures
206
+
207
+ ```typescript
208
+ // ═══════════════════════════════════════════════════════════
209
+ // ARRANGE-ACT-ASSERT Pattern
210
+ // ═══════════════════════════════════════════════════════════
211
+
212
+ describe('ShoppingCart', () => {
213
+ describe('addItem', () => {
214
+ it('increases total when item is added', () => {
215
+ // ARRANGE: Set up test data and dependencies
216
+ const cart = new ShoppingCart();
217
+ const item = { id: '1', name: 'Widget', price: 10.00 };
218
+
219
+ // ACT: Execute the behavior being tested
220
+ cart.addItem(item);
221
+
222
+ // ASSERT: Verify the expected outcome
223
+ expect(cart.total).toBe(10.00);
224
+ expect(cart.itemCount).toBe(1);
225
+ });
226
+ });
227
+ });
228
+
229
+ // ═══════════════════════════════════════════════════════════
230
+ // GIVEN-WHEN-THEN Pattern (BDD Style)
231
+ // ═══════════════════════════════════════════════════════════
232
+
233
+ describe('ShoppingCart', () => {
234
+ describe('when applying discount code', () => {
235
+ it('reduces total by discount percentage', () => {
236
+ // GIVEN a cart with items
237
+ const cart = new ShoppingCart();
238
+ cart.addItem({ id: '1', name: 'Widget', price: 100.00 });
239
+
240
+ // WHEN a 20% discount is applied
241
+ cart.applyDiscount('SAVE20');
242
+
243
+ // THEN the total is reduced by 20%
244
+ expect(cart.total).toBe(80.00);
245
+ });
246
+ });
247
+ });
248
+
249
+ // ═══════════════════════════════════════════════════════════
250
+ // Test Fixture Pattern
251
+ // ═══════════════════════════════════════════════════════════
252
+
253
+ describe('OrderService', () => {
254
+ // Shared fixtures
255
+ let orderService: OrderService;
256
+ let mockPaymentGateway: jest.Mocked<PaymentGateway>;
257
+ let mockInventory: jest.Mocked<InventoryService>;
258
+
259
+ // Common test data
260
+ const validOrder: Order = {
261
+ id: 'order-123',
262
+ items: [{ productId: 'prod-1', quantity: 2, price: 25.00 }],
263
+ total: 50.00,
264
+ };
265
+
266
+ beforeEach(() => {
267
+ // Fresh mocks for each test
268
+ mockPaymentGateway = {
269
+ charge: jest.fn().mockResolvedValue({ success: true }),
270
+ };
271
+ mockInventory = {
272
+ reserve: jest.fn().mockResolvedValue(true),
273
+ release: jest.fn().mockResolvedValue(true),
274
+ };
275
+
276
+ orderService = new OrderService(mockPaymentGateway, mockInventory);
277
+ });
278
+
279
+ afterEach(() => {
280
+ jest.clearAllMocks();
281
+ });
282
+
283
+ describe('submitOrder', () => {
284
+ it('reserves inventory and charges payment', async () => {
285
+ await orderService.submitOrder(validOrder);
286
+
287
+ expect(mockInventory.reserve).toHaveBeenCalledWith(validOrder.items);
288
+ expect(mockPaymentGateway.charge).toHaveBeenCalledWith(50.00);
289
+ });
290
+
291
+ it('releases inventory if payment fails', async () => {
292
+ mockPaymentGateway.charge.mockResolvedValue({ success: false });
293
+
294
+ await expect(orderService.submitOrder(validOrder)).rejects.toThrow();
295
+
296
+ expect(mockInventory.release).toHaveBeenCalledWith(validOrder.items);
297
+ });
298
+ });
299
+ });
300
+
301
+ // ═══════════════════════════════════════════════════════════
302
+ // Parameterized Tests
303
+ // ═══════════════════════════════════════════════════════════
304
+
305
+ describe('EmailValidator', () => {
306
+ // Valid email test cases
307
+ it.each([
308
+ ['simple@example.com', 'simple email'],
309
+ ['user.name@domain.org', 'email with dots'],
310
+ ['user+tag@example.com', 'email with plus'],
311
+ ['user@subdomain.domain.com', 'email with subdomain'],
312
+ ])('validates %s as valid (%s)', (email, _description) => {
313
+ expect(isValidEmail(email)).toBe(true);
314
+ });
315
+
316
+ // Invalid email test cases
317
+ it.each([
318
+ ['plaintext', 'no @ symbol'],
319
+ ['missing@domain', 'no TLD'],
320
+ ['@nodomain.com', 'no local part'],
321
+ ['spaces in@email.com', 'contains spaces'],
322
+ ['', 'empty string'],
323
+ ])('rejects %s as invalid (%s)', (email, _description) => {
324
+ expect(isValidEmail(email)).toBe(false);
325
+ });
326
+ });
327
+
328
+ // ═══════════════════════════════════════════════════════════
329
+ // Builder Pattern for Test Data
330
+ // ═══════════════════════════════════════════════════════════
331
+
332
+ class UserBuilder {
333
+ private user: Partial<User> = {
334
+ id: 'user-123',
335
+ email: 'test@example.com',
336
+ name: 'Test User',
337
+ role: 'user',
338
+ active: true,
339
+ };
340
+
341
+ withId(id: string): this {
342
+ this.user.id = id;
343
+ return this;
344
+ }
345
+
346
+ withEmail(email: string): this {
347
+ this.user.email = email;
348
+ return this;
349
+ }
350
+
351
+ withRole(role: UserRole): this {
352
+ this.user.role = role;
353
+ return this;
354
+ }
355
+
356
+ inactive(): this {
357
+ this.user.active = false;
358
+ return this;
359
+ }
360
+
361
+ admin(): this {
362
+ this.user.role = 'admin';
363
+ return this;
364
+ }
365
+
366
+ build(): User {
367
+ return this.user as User;
368
+ }
369
+ }
370
+
371
+ // Usage in tests
372
+ describe('PermissionService', () => {
373
+ it('grants admin access to admin users', () => {
374
+ const admin = new UserBuilder().admin().build();
375
+ const service = new PermissionService();
376
+
377
+ expect(service.canAccessAdminPanel(admin)).toBe(true);
378
+ });
379
+
380
+ it('denies admin access to regular users', () => {
381
+ const user = new UserBuilder().withRole('user').build();
382
+ const service = new PermissionService();
383
+
384
+ expect(service.canAccessAdminPanel(user)).toBe(false);
385
+ });
386
+ });
387
+ ```
388
+
389
+ ### 4. Mocking Strategies
390
+
391
+ ```typescript
392
+ // ═══════════════════════════════════════════════════════════
393
+ // Dependency Injection for Testability
394
+ // ═══════════════════════════════════════════════════════════
395
+
396
+ // BAD: Hard to test
397
+ class UserService {
398
+ async getUser(id: string) {
399
+ const response = await fetch(`/api/users/${id}`); // Can't mock
400
+ return response.json();
401
+ }
402
+ }
403
+
404
+ // GOOD: Dependency injection
405
+ interface HttpClient {
406
+ get<T>(url: string): Promise<T>;
407
+ }
408
+
409
+ class UserService {
410
+ constructor(private http: HttpClient) {}
411
+
412
+ async getUser(id: string): Promise<User> {
413
+ return this.http.get<User>(`/api/users/${id}`);
414
+ }
415
+ }
416
+
417
+ // Test with mock
418
+ describe('UserService', () => {
419
+ it('fetches user by id', async () => {
420
+ const mockHttp: jest.Mocked<HttpClient> = {
421
+ get: jest.fn().mockResolvedValue({ id: '1', name: 'John' }),
422
+ };
423
+
424
+ const service = new UserService(mockHttp);
425
+ const user = await service.getUser('1');
426
+
427
+ expect(mockHttp.get).toHaveBeenCalledWith('/api/users/1');
428
+ expect(user.name).toBe('John');
429
+ });
430
+ });
431
+
432
+ // ═══════════════════════════════════════════════════════════
433
+ // Stub vs Mock vs Spy
434
+ // ═══════════════════════════════════════════════════════════
435
+
436
+ // STUB: Provides canned responses
437
+ const stubLogger: Logger = {
438
+ log: () => {},
439
+ error: () => {},
440
+ warn: () => {},
441
+ };
442
+
443
+ // MOCK: Verifies interactions
444
+ const mockLogger = {
445
+ log: jest.fn(),
446
+ error: jest.fn(),
447
+ warn: jest.fn(),
448
+ };
449
+
450
+ // SPY: Wraps real implementation
451
+ const realLogger = new ConsoleLogger();
452
+ const spyLogger = jest.spyOn(realLogger, 'error');
453
+
454
+ // ═══════════════════════════════════════════════════════════
455
+ // Time-Based Testing
456
+ // ═══════════════════════════════════════════════════════════
457
+
458
+ describe('TokenService', () => {
459
+ beforeEach(() => {
460
+ jest.useFakeTimers();
461
+ });
462
+
463
+ afterEach(() => {
464
+ jest.useRealTimers();
465
+ });
466
+
467
+ it('expires tokens after 1 hour', () => {
468
+ const service = new TokenService();
469
+ const token = service.createToken('user-1');
470
+
471
+ // Initially valid
472
+ expect(service.isValid(token)).toBe(true);
473
+
474
+ // Advance time by 59 minutes - still valid
475
+ jest.advanceTimersByTime(59 * 60 * 1000);
476
+ expect(service.isValid(token)).toBe(true);
477
+
478
+ // Advance time by 2 more minutes - now expired
479
+ jest.advanceTimersByTime(2 * 60 * 1000);
480
+ expect(service.isValid(token)).toBe(false);
481
+ });
482
+ });
483
+
484
+ // ═══════════════════════════════════════════════════════════
485
+ // Testing Async Code
486
+ // ═══════════════════════════════════════════════════════════
487
+
488
+ describe('NotificationService', () => {
489
+ it('sends notification and waits for delivery', async () => {
490
+ const service = new NotificationService();
491
+
492
+ // Test async success
493
+ const result = await service.send('Hello');
494
+ expect(result.delivered).toBe(true);
495
+ });
496
+
497
+ it('handles async errors', async () => {
498
+ const service = new NotificationService();
499
+ service.setOffline(true);
500
+
501
+ // Test async error
502
+ await expect(service.send('Hello')).rejects.toThrow('Network unavailable');
503
+ });
504
+
505
+ it('retries failed deliveries', async () => {
506
+ const mockTransport = {
507
+ send: jest
508
+ .fn()
509
+ .mockRejectedValueOnce(new Error('Temporary failure'))
510
+ .mockResolvedValueOnce({ success: true }),
511
+ };
512
+
513
+ const service = new NotificationService(mockTransport);
514
+ await service.send('Hello');
515
+
516
+ expect(mockTransport.send).toHaveBeenCalledTimes(2);
517
+ });
518
+ });
519
+ ```
520
+
521
+ ### 5. Coverage and Quality Metrics
522
+
523
+ ```markdown
524
+ ## Code Coverage Guidelines
525
+
526
+ ### Coverage Targets by Type
527
+
528
+ | Test Type | Target | Rationale |
529
+ |-----------|--------|-----------|
530
+ | Unit Tests | 80%+ | Core logic coverage |
531
+ | Integration | 60%+ | Critical paths |
532
+ | E2E | 100% critical | User journeys |
533
+
534
+ ### What Coverage Tells You
535
+ ✅ Code that IS executed by tests
536
+ ❌ Does NOT mean code is well-tested
537
+ ❌ Does NOT mean edge cases covered
538
+ ❌ Does NOT mean behavior is verified
539
+
540
+ ### Meaningful Coverage
541
+ ```typescript
542
+ // 100% coverage but NOT well-tested
543
+ function divide(a: number, b: number): number {
544
+ return a / b;
545
+ }
546
+
547
+ it('divides two numbers', () => {
548
+ expect(divide(10, 2)).toBe(5); // 100% coverage!
549
+ });
550
+
551
+ // Missing tests:
552
+ // - divide(10, 0) - Division by zero
553
+ // - divide(0, 5) - Zero numerator
554
+ // - divide(-10, 2) - Negative numbers
555
+ // - divide(1.5, 0.3) - Floating point
556
+ ```
557
+
558
+ ### Coverage Report Analysis
559
+ ```bash
560
+ # Generate coverage report
561
+ npm test -- --coverage
562
+
563
+ # Example output:
564
+ # -------------------|---------|----------|---------|---------|
565
+ # File | % Stmts | % Branch | % Funcs | % Lines |
566
+ # -------------------|---------|----------|---------|---------|
567
+ # All files | 85.32 | 72.41 | 91.23 | 84.76 |
568
+ # src/services | 92.14 | 85.00 | 95.00 | 91.89 |
569
+ # UserService.ts | 95.00 | 90.00 | 100.00 | 94.74 |
570
+ # OrderService.ts | 89.47 | 80.00 | 90.00 | 89.19 |
571
+ ```
572
+
573
+ ### Branch Coverage Focus
20
574
  ```typescript
21
- // 1. RED - Write test first
22
- describe('calculateTax', () => {
23
- it('calculates 10% tax', () => {
24
- expect(calculateTax(100, 0.1)).toBe(10);
575
+ // Branch coverage example
576
+ function processOrder(order: Order): Result {
577
+ if (order.total > 1000) { // Branch 1
578
+ if (order.customerType === 'vip') { // Branch 2
579
+ return applyVipDiscount(order);
580
+ }
581
+ return applyBulkDiscount(order);
582
+ }
583
+ return processStandard(order);
584
+ }
585
+
586
+ // Tests needed for 100% branch coverage:
587
+ // 1. order.total <= 1000
588
+ // 2. order.total > 1000 AND customerType === 'vip'
589
+ // 3. order.total > 1000 AND customerType !== 'vip'
590
+ ```
591
+
592
+ ### 6. TDD with Different Architectures
593
+
594
+ ```typescript
595
+ // ═══════════════════════════════════════════════════════════
596
+ // TDD with Clean Architecture
597
+ // ═══════════════════════════════════════════════════════════
598
+
599
+ // 1. Start with USE CASE test
600
+ describe('CreateUserUseCase', () => {
601
+ it('creates user and sends welcome email', async () => {
602
+ // Arrange
603
+ const mockUserRepo: jest.Mocked<UserRepository> = {
604
+ save: jest.fn().mockResolvedValue({ id: '1', email: 'test@test.com' }),
605
+ findByEmail: jest.fn().mockResolvedValue(null),
606
+ };
607
+ const mockEmailService: jest.Mocked<EmailService> = {
608
+ send: jest.fn().mockResolvedValue(true),
609
+ };
610
+
611
+ const useCase = new CreateUserUseCase(mockUserRepo, mockEmailService);
612
+
613
+ // Act
614
+ const result = await useCase.execute({
615
+ email: 'test@test.com',
616
+ password: 'password123',
617
+ });
618
+
619
+ // Assert
620
+ expect(result.success).toBe(true);
621
+ expect(mockUserRepo.save).toHaveBeenCalled();
622
+ expect(mockEmailService.send).toHaveBeenCalledWith(
623
+ 'test@test.com',
624
+ expect.stringContaining('Welcome')
625
+ );
626
+ });
627
+ });
628
+
629
+ // 2. Then implement the use case
630
+ class CreateUserUseCase {
631
+ constructor(
632
+ private userRepo: UserRepository,
633
+ private emailService: EmailService
634
+ ) {}
635
+
636
+ async execute(input: CreateUserInput): Promise<CreateUserOutput> {
637
+ // Check if user exists
638
+ const existing = await this.userRepo.findByEmail(input.email);
639
+ if (existing) {
640
+ return { success: false, error: 'Email already registered' };
641
+ }
642
+
643
+ // Create user
644
+ const user = await this.userRepo.save({
645
+ email: input.email,
646
+ passwordHash: await hash(input.password),
647
+ });
648
+
649
+ // Send welcome email
650
+ await this.emailService.send(user.email, 'Welcome to our platform!');
651
+
652
+ return { success: true, userId: user.id };
653
+ }
654
+ }
655
+
656
+ // ═══════════════════════════════════════════════════════════
657
+ // TDD with API Endpoints
658
+ // ═══════════════════════════════════════════════════════════
659
+
660
+ describe('POST /api/users', () => {
661
+ it('creates user and returns 201', async () => {
662
+ const response = await request(app)
663
+ .post('/api/users')
664
+ .send({
665
+ email: 'new@example.com',
666
+ password: 'securepass123',
667
+ })
668
+ .expect(201);
669
+
670
+ expect(response.body).toMatchObject({
671
+ id: expect.any(String),
672
+ email: 'new@example.com',
673
+ });
674
+ });
675
+
676
+ it('returns 400 for invalid email', async () => {
677
+ const response = await request(app)
678
+ .post('/api/users')
679
+ .send({
680
+ email: 'not-an-email',
681
+ password: 'securepass123',
682
+ })
683
+ .expect(400);
684
+
685
+ expect(response.body.error).toContain('email');
686
+ });
687
+
688
+ it('returns 409 for duplicate email', async () => {
689
+ // Create user first
690
+ await createUser({ email: 'exists@example.com' });
691
+
692
+ // Try to create duplicate
693
+ const response = await request(app)
694
+ .post('/api/users')
695
+ .send({
696
+ email: 'exists@example.com',
697
+ password: 'securepass123',
698
+ })
699
+ .expect(409);
700
+
701
+ expect(response.body.error).toContain('already exists');
702
+ });
703
+ });
704
+ ```
705
+
706
+ ## Use Cases
707
+
708
+ ### Building a Calculator with TDD
709
+
710
+ ```typescript
711
+ // Progressive TDD session building a calculator
712
+
713
+ // Test 1: Addition
714
+ describe('Calculator', () => {
715
+ describe('add', () => {
716
+ it('adds two positive numbers', () => {
717
+ expect(add(2, 3)).toBe(5);
718
+ });
719
+ });
720
+ });
721
+
722
+ // Implementation 1
723
+ function add(a: number, b: number): number {
724
+ return a + b;
725
+ }
726
+
727
+ // Test 2: Negative numbers
728
+ it('adds negative numbers', () => {
729
+ expect(add(-2, -3)).toBe(-5);
730
+ expect(add(-2, 3)).toBe(1);
731
+ });
732
+ // Already passes!
733
+
734
+ // Test 3: Subtraction
735
+ describe('subtract', () => {
736
+ it('subtracts two numbers', () => {
737
+ expect(subtract(5, 3)).toBe(2);
25
738
  });
26
739
  });
27
740
 
28
- // 2. GREEN - Minimal implementation
29
- function calculateTax(amount: number, rate: number) {
30
- return amount * rate;
741
+ // Implementation 3
742
+ function subtract(a: number, b: number): number {
743
+ return a - b;
744
+ }
745
+
746
+ // Refactor: Extract to class
747
+ class Calculator {
748
+ add(a: number, b: number): number {
749
+ return a + b;
750
+ }
751
+
752
+ subtract(a: number, b: number): number {
753
+ return a - b;
754
+ }
31
755
  }
32
756
 
33
- // 3. REFACTOR if needed
757
+ // Continue with multiply, divide, etc.
34
758
  ```
35
759
 
36
- ## Benefits
37
- - Design emerges from tests
38
- - 100% coverage by default
39
- - Confidence in refactoring
760
+ ## Best Practices
761
+
762
+ ### Do's
763
+
764
+ - Write the test BEFORE the implementation
765
+ - Keep tests focused on ONE behavior
766
+ - Use descriptive test names that document behavior
767
+ - Run tests frequently (every few minutes)
768
+ - Refactor only when tests are green
769
+ - Test behavior, not implementation
770
+ - Keep the Red-Green-Refactor cycle short
771
+ - Use test doubles (mocks) for external dependencies
772
+ - Organize tests to mirror source structure
773
+ - Delete tests that no longer add value
774
+
775
+ ### Don'ts
776
+
777
+ - Don't write implementation before tests
778
+ - Don't test private methods directly
779
+ - Don't mock what you don't own
780
+ - Don't skip the refactor step
781
+ - Don't write tests for trivial code
782
+ - Don't let tests become too slow
783
+ - Don't test framework/library code
784
+ - Don't ignore flaky tests
785
+ - Don't over-mock (test integration too)
786
+ - Don't forget to run all tests before commit
787
+
788
+ ## References
789
+
790
+ - [Test Driven Development by Kent Beck](https://www.amazon.com/Test-Driven-Development-Kent-Beck/dp/0321146530)
791
+ - [Growing Object-Oriented Software, Guided by Tests](http://www.growing-object-oriented-software.com/)
792
+ - [The Art of Unit Testing](https://www.manning.com/books/the-art-of-unit-testing-third-edition)
793
+ - [Jest Documentation](https://jestjs.io/docs/getting-started)
794
+ - [Testing Library](https://testing-library.com/)