metacoding 1.5.1 → 2.0.1

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 (95) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +108 -514
  3. package/lib/cli.d.ts.map +1 -1
  4. package/lib/cli.js +18 -19
  5. package/lib/cli.js.map +1 -1
  6. package/lib/commands/init.d.ts +8 -14
  7. package/lib/commands/init.d.ts.map +1 -1
  8. package/lib/commands/init.js +105 -387
  9. package/lib/commands/init.js.map +1 -1
  10. package/lib/commands/update.d.ts +9 -9
  11. package/lib/commands/update.d.ts.map +1 -1
  12. package/lib/commands/update.js +141 -320
  13. package/lib/commands/update.js.map +1 -1
  14. package/lib/services/backup.d.ts +1 -1
  15. package/lib/services/backup.d.ts.map +1 -1
  16. package/lib/services/backup.js +10 -6
  17. package/lib/services/backup.js.map +1 -1
  18. package/lib/services/filesystem.d.ts.map +1 -1
  19. package/lib/services/filesystem.js +11 -5
  20. package/lib/services/filesystem.js.map +1 -1
  21. package/lib/services/gitignore-manager.js +5 -5
  22. package/lib/services/gitignore-manager.js.map +1 -1
  23. package/lib/services/project-detector.d.ts +9 -8
  24. package/lib/services/project-detector.d.ts.map +1 -1
  25. package/lib/services/project-detector.js +79 -197
  26. package/lib/services/project-detector.js.map +1 -1
  27. package/lib/services/skill-manager.d.ts +23 -0
  28. package/lib/services/skill-manager.d.ts.map +1 -0
  29. package/lib/services/skill-manager.js +212 -0
  30. package/lib/services/skill-manager.js.map +1 -0
  31. package/lib/types/index.d.ts +5 -15
  32. package/lib/types/index.d.ts.map +1 -1
  33. package/package.json +9 -17
  34. package/skills/metacoding-workflow/SKILL.md +52 -0
  35. package/skills/metacoding-workflow/agents/openai.yaml +4 -0
  36. package/skills/metacoding-workflow/assets/templates/changelog-entry.md +6 -0
  37. package/skills/metacoding-workflow/assets/templates/project-context.md +18 -0
  38. package/skills/metacoding-workflow/assets/templates/repeated-task-checklist.md +8 -0
  39. package/skills/metacoding-workflow/assets/templates/task-entry.md +9 -0
  40. package/skills/metacoding-workflow/assets/templates/test-plan.md +8 -0
  41. package/skills/metacoding-workflow/references/javascript.md +7 -0
  42. package/skills/metacoding-workflow/references/node.md +7 -0
  43. package/skills/metacoding-workflow/references/platform-adaptation.md +37 -0
  44. package/skills/metacoding-workflow/references/python.md +7 -0
  45. package/skills/metacoding-workflow/references/react.md +7 -0
  46. package/skills/metacoding-workflow/references/repository-organization.md +84 -0
  47. package/skills/metacoding-workflow/references/typescript.md +7 -0
  48. package/skills/metacoding-workflow/references/workflow-rules.md +54 -0
  49. package/skills/vendor-templates/claude-agent.md.template +41 -0
  50. package/lib/services/assistant-adapter.d.ts +0 -18
  51. package/lib/services/assistant-adapter.d.ts.map +0 -1
  52. package/lib/services/assistant-adapter.js +0 -246
  53. package/lib/services/assistant-adapter.js.map +0 -1
  54. package/lib/services/cursor.d.ts +0 -47
  55. package/lib/services/cursor.d.ts.map +0 -1
  56. package/lib/services/cursor.js +0 -314
  57. package/lib/services/cursor.js.map +0 -1
  58. package/lib/services/template-manager.d.ts +0 -23
  59. package/lib/services/template-manager.d.ts.map +0 -1
  60. package/lib/services/template-manager.js +0 -374
  61. package/lib/services/template-manager.js.map +0 -1
  62. package/lib/services/vscode.d.ts +0 -10
  63. package/lib/services/vscode.d.ts.map +0 -1
  64. package/lib/services/vscode.js +0 -108
  65. package/lib/services/vscode.js.map +0 -1
  66. package/templates/assistants/AGENTS.md +0 -203
  67. package/templates/assistants/CLAUDE.md +0 -156
  68. package/templates/assistants/GEMINI.md +0 -193
  69. package/templates/general/code-review.instructions.md +0 -265
  70. package/templates/general/copilot-instructions.md +0 -427
  71. package/templates/general/docs-update.instructions.md +0 -275
  72. package/templates/general/release.instructions.md +0 -242
  73. package/templates/general/template.json +0 -9
  74. package/templates/general/test-runner.instructions.md +0 -188
  75. package/templates/javascript/javascript.coding.instructions.md +0 -500
  76. package/templates/javascript/javascript.docs.instructions.md +0 -563
  77. package/templates/javascript/javascript.testing.instructions.md +0 -686
  78. package/templates/javascript/template.json +0 -36
  79. package/templates/node/nodejs.coding.instructions.md +0 -249
  80. package/templates/node/nodejs.docs.instructions.md +0 -261
  81. package/templates/node/nodejs.testing.instructions.md +0 -373
  82. package/templates/node/template.json +0 -23
  83. package/templates/python/python.coding.instructions.md +0 -338
  84. package/templates/python/python.docs.instructions.md +0 -1178
  85. package/templates/python/python.testing.instructions.md +0 -1073
  86. package/templates/python/template.json +0 -75
  87. package/templates/react/react.coding.instructions.md +0 -694
  88. package/templates/react/react.docs.instructions.md +0 -451
  89. package/templates/react/react.testing.instructions.md +0 -192
  90. package/templates/react/template.json +0 -14
  91. package/templates/react/test-runner.instructions.md +0 -135
  92. package/templates/typescript/template.json +0 -16
  93. package/templates/typescript/typescript.coding.instructions.md +0 -368
  94. package/templates/typescript/typescript.docs.instructions.md +0 -760
  95. package/templates/typescript/typescript.testing.instructions.md +0 -739
@@ -1,739 +0,0 @@
1
- ---
2
- description: 'TypeScript/Node.js-specific testing patterns and frameworks'
3
- applyTo: 'test/**/*.{ts,js}'
4
- ---
5
-
6
- # TypeScript/Node.js Testing Standards
7
-
8
- ## Testing Framework and Setup
9
-
10
- ### Primary Testing Framework: Jest
11
-
12
- ```typescript
13
- // jest.config.js
14
- module.exports = {
15
- preset: 'ts-jest',
16
- testEnvironment: 'node',
17
- roots: ['<rootDir>/src', '<rootDir>/test'],
18
- testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
19
- transform: {
20
- '^.+\\.ts$': 'ts-jest',
21
- },
22
- collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts'],
23
- coverageDirectory: 'coverage',
24
- coverageReporters: ['text', 'lcov', 'html'],
25
- };
26
- ```
27
-
28
- ### Test Case Naming Conventions
29
-
30
- **Area Prefixes for Node.js/Backend:**
31
-
32
- - `API` - API endpoint tests
33
- - `SRV` - Service layer tests
34
- - `DB` - Database/Data layer tests
35
- - `MW` - Middleware tests
36
- - `ROUTE` - Routing logic tests
37
- - `AUTH` - Authentication/Authorization tests
38
- - `UTIL` - Utility function tests
39
- - `CONFIG` - Configuration management tests
40
- - `CORE` - Core business logic tests
41
- - `INT` - Integration tests
42
- - `E2E` - End-to-end tests
43
-
44
- **Examples:**
45
-
46
- - `API-UNIT-001` - First unit test for API endpoints
47
- - `SRV-UNIT-001` - First unit test for Service layer
48
- - `DB-INT-001` - First integration test for Database
49
- - `E2E-FLOW-001` - First end-to-end workflow test
50
-
51
- ## Unit Testing Patterns
52
-
53
- ### Service Layer Testing
54
-
55
- ```typescript
56
- // test/unit/user-service.test.ts
57
- import { UserService } from '../../src/services/user-service';
58
- import { UserRepository } from '../../src/repositories/user-repository';
59
- import { User } from '../../src/types/user';
60
-
61
- // Mock the repository
62
- jest.mock('../../src/repositories/user-repository');
63
- const MockUserRepository = UserRepository as jest.MockedClass<
64
- typeof UserRepository
65
- >;
66
-
67
- describe('UserService', () => {
68
- let userService: UserService;
69
- let mockUserRepository: jest.Mocked<UserRepository>;
70
-
71
- beforeEach(() => {
72
- mockUserRepository =
73
- new MockUserRepository() as jest.Mocked<UserRepository>;
74
- userService = new UserService(mockUserRepository);
75
- });
76
-
77
- afterEach(() => {
78
- jest.clearAllMocks();
79
- });
80
-
81
- describe('getUserById', () => {
82
- it('should return user when user exists', async () => {
83
- // Arrange
84
- const userId = '123';
85
- const expectedUser: User = {
86
- id: userId,
87
- name: 'John Doe',
88
- email: 'john@example.com',
89
- };
90
- mockUserRepository.findById.mockResolvedValue(expectedUser);
91
-
92
- // Act
93
- const result = await userService.getUserById(userId);
94
-
95
- // Assert
96
- expect(result).toEqual(expectedUser);
97
- expect(mockUserRepository.findById).toHaveBeenCalledWith(userId);
98
- expect(mockUserRepository.findById).toHaveBeenCalledTimes(1);
99
- });
100
-
101
- it('should throw error when user not found', async () => {
102
- // Arrange
103
- const userId = '999';
104
- mockUserRepository.findById.mockResolvedValue(null);
105
-
106
- // Act & Assert
107
- await expect(userService.getUserById(userId)).rejects.toThrow(
108
- 'User not found'
109
- );
110
-
111
- expect(mockUserRepository.findById).toHaveBeenCalledWith(userId);
112
- });
113
- });
114
- });
115
- ```
116
-
117
- ### API Endpoint Testing
118
-
119
- ```typescript
120
- // test/unit/user-controller.test.ts
121
- import request from 'supertest';
122
- import { app } from '../../src/app';
123
- import { UserService } from '../../src/services/user-service';
124
-
125
- // Mock the service
126
- jest.mock('../../src/services/user-service');
127
- const MockUserService = UserService as jest.MockedClass<typeof UserService>;
128
-
129
- describe('User Controller', () => {
130
- let mockUserService: jest.Mocked<UserService>;
131
-
132
- beforeEach(() => {
133
- mockUserService = new MockUserService() as jest.Mocked<UserService>;
134
- // Inject mock into controller if using dependency injection
135
- });
136
-
137
- afterEach(() => {
138
- jest.clearAllMocks();
139
- });
140
-
141
- describe('GET /api/users/:id', () => {
142
- it('should return user when valid id provided', async () => {
143
- // Arrange
144
- const userId = '123';
145
- const mockUser = {
146
- id: userId,
147
- name: 'John Doe',
148
- email: 'john@example.com',
149
- };
150
- mockUserService.getUserById.mockResolvedValue(mockUser);
151
-
152
- // Act
153
- const response = await request(app)
154
- .get(`/api/users/${userId}`)
155
- .expect(200);
156
-
157
- // Assert
158
- expect(response.body).toEqual(mockUser);
159
- expect(mockUserService.getUserById).toHaveBeenCalledWith(userId);
160
- });
161
-
162
- it('should return 404 when user not found', async () => {
163
- // Arrange
164
- const userId = '999';
165
- mockUserService.getUserById.mockRejectedValue(
166
- new Error('User not found')
167
- );
168
-
169
- // Act
170
- const response = await request(app)
171
- .get(`/api/users/${userId}`)
172
- .expect(404);
173
-
174
- // Assert
175
- expect(response.body).toEqual({ error: 'User not found' });
176
- });
177
-
178
- it('should return 400 when invalid id format', async () => {
179
- // Arrange
180
- const invalidId = 'invalid-id';
181
-
182
- // Act
183
- const response = await request(app)
184
- .get(`/api/users/${invalidId}`)
185
- .expect(400);
186
-
187
- // Assert
188
- expect(response.body).toEqual({ error: 'Invalid user ID format' });
189
- });
190
- });
191
- });
192
- ```
193
-
194
- ### Utility Function Testing
195
-
196
- ```typescript
197
- // test/unit/string-utils.test.ts
198
- import { capitalize, slugify, truncate } from '../../src/utils/string-utils';
199
-
200
- describe('String Utils', () => {
201
- describe('capitalize', () => {
202
- it('should capitalize first letter of string', () => {
203
- expect(capitalize('hello')).toBe('Hello');
204
- });
205
-
206
- it('should handle empty string', () => {
207
- expect(capitalize('')).toBe('');
208
- });
209
-
210
- it('should handle single character', () => {
211
- expect(capitalize('a')).toBe('A');
212
- });
213
-
214
- it('should not change already capitalized string', () => {
215
- expect(capitalize('Hello')).toBe('Hello');
216
- });
217
- });
218
-
219
- describe('slugify', () => {
220
- it('should convert spaces to hyphens', () => {
221
- expect(slugify('Hello World')).toBe('hello-world');
222
- });
223
-
224
- it('should remove special characters', () => {
225
- expect(slugify('Hello! @World#')).toBe('hello-world');
226
- });
227
-
228
- it('should handle multiple spaces', () => {
229
- expect(slugify('Hello World')).toBe('hello-world');
230
- });
231
- });
232
-
233
- describe('truncate', () => {
234
- it('should truncate long strings', () => {
235
- const longString = 'This is a very long string that should be truncated';
236
- expect(truncate(longString, 20)).toBe('This is a very lo...');
237
- });
238
-
239
- it('should not truncate short strings', () => {
240
- const shortString = 'Short';
241
- expect(truncate(shortString, 20)).toBe('Short');
242
- });
243
- });
244
- });
245
- ```
246
-
247
- ## Integration Testing
248
-
249
- ### Database Integration Testing
250
-
251
- ```typescript
252
- // test/integration/user-repository.test.ts
253
- import { UserRepository } from '../../src/repositories/user-repository';
254
- import { DatabaseConnection } from '../../src/database/connection';
255
- import { User } from '../../src/types/user';
256
-
257
- describe('UserRepository Integration', () => {
258
- let userRepository: UserRepository;
259
- let db: DatabaseConnection;
260
-
261
- beforeAll(async () => {
262
- // Setup test database
263
- db = new DatabaseConnection(process.env.TEST_DATABASE_URL);
264
- await db.connect();
265
- userRepository = new UserRepository(db);
266
- });
267
-
268
- afterAll(async () => {
269
- await db.disconnect();
270
- });
271
-
272
- beforeEach(async () => {
273
- // Clean database before each test
274
- await db.query('DELETE FROM users');
275
- });
276
-
277
- describe('findById', () => {
278
- it('should find existing user', async () => {
279
- // Arrange
280
- const userData: Omit<User, 'id'> = {
281
- name: 'John Doe',
282
- email: 'john@example.com',
283
- };
284
- const createdUser = await userRepository.create(userData);
285
-
286
- // Act
287
- const foundUser = await userRepository.findById(createdUser.id);
288
-
289
- // Assert
290
- expect(foundUser).toEqual(createdUser);
291
- });
292
-
293
- it('should return null for non-existent user', async () => {
294
- // Act
295
- const foundUser = await userRepository.findById('999');
296
-
297
- // Assert
298
- expect(foundUser).toBeNull();
299
- });
300
- });
301
-
302
- describe('create', () => {
303
- it('should create new user with generated id', async () => {
304
- // Arrange
305
- const userData: Omit<User, 'id'> = {
306
- name: 'Jane Doe',
307
- email: 'jane@example.com',
308
- };
309
-
310
- // Act
311
- const createdUser = await userRepository.create(userData);
312
-
313
- // Assert
314
- expect(createdUser).toMatchObject(userData);
315
- expect(createdUser.id).toBeDefined();
316
- expect(typeof createdUser.id).toBe('string');
317
- });
318
-
319
- it('should enforce unique email constraint', async () => {
320
- // Arrange
321
- const userData: Omit<User, 'id'> = {
322
- name: 'John Doe',
323
- email: 'duplicate@example.com',
324
- };
325
- await userRepository.create(userData);
326
-
327
- // Act & Assert
328
- await expect(userRepository.create(userData)).rejects.toThrow(
329
- 'Email already exists'
330
- );
331
- });
332
- });
333
- });
334
- ```
335
-
336
- ### API Integration Testing
337
-
338
- ```typescript
339
- // test/integration/user-api.test.ts
340
- import request from 'supertest';
341
- import { app } from '../../src/app';
342
- import { DatabaseConnection } from '../../src/database/connection';
343
-
344
- describe('User API Integration', () => {
345
- let db: DatabaseConnection;
346
-
347
- beforeAll(async () => {
348
- db = new DatabaseConnection(process.env.TEST_DATABASE_URL);
349
- await db.connect();
350
- });
351
-
352
- afterAll(async () => {
353
- await db.disconnect();
354
- });
355
-
356
- beforeEach(async () => {
357
- await db.query('DELETE FROM users');
358
- });
359
-
360
- describe('POST /api/users', () => {
361
- it('should create new user and return 201', async () => {
362
- // Arrange
363
- const userData = {
364
- name: 'John Doe',
365
- email: 'john@example.com',
366
- };
367
-
368
- // Act
369
- const response = await request(app)
370
- .post('/api/users')
371
- .send(userData)
372
- .expect(201);
373
-
374
- // Assert
375
- expect(response.body).toMatchObject(userData);
376
- expect(response.body.id).toBeDefined();
377
- });
378
-
379
- it('should validate required fields', async () => {
380
- // Arrange
381
- const invalidData = {
382
- name: 'John Doe',
383
- // Missing email
384
- };
385
-
386
- // Act
387
- const response = await request(app)
388
- .post('/api/users')
389
- .send(invalidData)
390
- .expect(400);
391
-
392
- // Assert
393
- expect(response.body.error).toContain('email is required');
394
- });
395
- });
396
-
397
- describe('GET /api/users', () => {
398
- it('should return list of users', async () => {
399
- // Arrange
400
- const users = [
401
- { name: 'John Doe', email: 'john@example.com' },
402
- { name: 'Jane Doe', email: 'jane@example.com' },
403
- ];
404
-
405
- for (const user of users) {
406
- await request(app).post('/api/users').send(user);
407
- }
408
-
409
- // Act
410
- const response = await request(app).get('/api/users').expect(200);
411
-
412
- // Assert
413
- expect(response.body).toHaveLength(2);
414
- expect(response.body[0]).toMatchObject(users[0]);
415
- expect(response.body[1]).toMatchObject(users[1]);
416
- });
417
- });
418
- });
419
- ```
420
-
421
- ## Mocking Strategies
422
-
423
- ### External Service Mocking
424
-
425
- ```typescript
426
- // test/mocks/external-api.ts
427
- import nock from 'nock';
428
-
429
- export const mockExternalAPI = {
430
- mockUserLookup: (userId: string, userData: any) => {
431
- return nock('https://api.external-service.com')
432
- .get(`/users/${userId}`)
433
- .reply(200, userData);
434
- },
435
-
436
- mockUserLookupError: (userId: string, statusCode: number = 500) => {
437
- return nock('https://api.external-service.com')
438
- .get(`/users/${userId}`)
439
- .reply(statusCode, { error: 'External service error' });
440
- },
441
-
442
- cleanup: () => {
443
- nock.cleanAll();
444
- },
445
- };
446
-
447
- // Usage in tests
448
- describe('External Service Integration', () => {
449
- afterEach(() => {
450
- mockExternalAPI.cleanup();
451
- });
452
-
453
- it('should handle external API success', async () => {
454
- // Arrange
455
- const userId = '123';
456
- const userData = { id: userId, name: 'John Doe' };
457
- mockExternalAPI.mockUserLookup(userId, userData);
458
-
459
- // Act & Assert
460
- const result = await externalService.getUser(userId);
461
- expect(result).toEqual(userData);
462
- });
463
- });
464
- ```
465
-
466
- ### File System Mocking
467
-
468
- ```typescript
469
- // test/unit/file-service.test.ts
470
- import fs from 'fs/promises';
471
- import { FileService } from '../../src/services/file-service';
472
-
473
- // Mock fs module
474
- jest.mock('fs/promises');
475
- const mockFs = fs as jest.Mocked<typeof fs>;
476
-
477
- describe('FileService', () => {
478
- let fileService: FileService;
479
-
480
- beforeEach(() => {
481
- fileService = new FileService();
482
- });
483
-
484
- afterEach(() => {
485
- jest.clearAllMocks();
486
- });
487
-
488
- describe('readConfig', () => {
489
- it('should read and parse JSON config file', async () => {
490
- // Arrange
491
- const configData = { host: 'localhost', port: 3000 };
492
- const configJson = JSON.stringify(configData);
493
- mockFs.readFile.mockResolvedValue(configJson);
494
-
495
- // Act
496
- const result = await fileService.readConfig('/path/to/config.json');
497
-
498
- // Assert
499
- expect(result).toEqual(configData);
500
- expect(mockFs.readFile).toHaveBeenCalledWith(
501
- '/path/to/config.json',
502
- 'utf8'
503
- );
504
- });
505
-
506
- it('should handle file read errors', async () => {
507
- // Arrange
508
- mockFs.readFile.mockRejectedValue(new Error('File not found'));
509
-
510
- // Act & Assert
511
- await expect(
512
- fileService.readConfig('/nonexistent/config.json')
513
- ).rejects.toThrow('File not found');
514
- });
515
- });
516
- });
517
- ```
518
-
519
- ## Async Testing Patterns
520
-
521
- ### Promise Testing
522
-
523
- ```typescript
524
- // test/unit/async-service.test.ts
525
- import { AsyncService } from '../../src/services/async-service';
526
-
527
- describe('AsyncService', () => {
528
- let asyncService: AsyncService;
529
-
530
- beforeEach(() => {
531
- asyncService = new AsyncService();
532
- });
533
-
534
- describe('processData', () => {
535
- it('should process data asynchronously', async () => {
536
- // Arrange
537
- const inputData = ['item1', 'item2', 'item3'];
538
-
539
- // Act
540
- const result = await asyncService.processData(inputData);
541
-
542
- // Assert
543
- expect(result).toHaveLength(3);
544
- expect(result[0]).toContain('processed');
545
- });
546
-
547
- it('should handle processing errors', async () => {
548
- // Arrange
549
- const invalidData = [null, undefined];
550
-
551
- // Act & Assert
552
- await expect(asyncService.processData(invalidData)).rejects.toThrow(
553
- 'Invalid data provided'
554
- );
555
- });
556
- });
557
-
558
- describe('processWithTimeout', () => {
559
- it('should complete within timeout', async () => {
560
- // Arrange
561
- const data = 'test data';
562
- const timeout = 1000;
563
-
564
- // Act
565
- const startTime = Date.now();
566
- const result = await asyncService.processWithTimeout(data, timeout);
567
- const endTime = Date.now();
568
-
569
- // Assert
570
- expect(result).toBeDefined();
571
- expect(endTime - startTime).toBeLessThan(timeout);
572
- });
573
-
574
- it('should timeout for long operations', async () => {
575
- // Arrange
576
- const data = 'long processing data';
577
- const shortTimeout = 100;
578
-
579
- // Act & Assert
580
- await expect(
581
- asyncService.processWithTimeout(data, shortTimeout)
582
- ).rejects.toThrow('Operation timed out');
583
- });
584
- });
585
- });
586
- ```
587
-
588
- ### Event Emitter Testing
589
-
590
- ```typescript
591
- // test/unit/event-service.test.ts
592
- import { EventService } from '../../src/services/event-service';
593
-
594
- describe('EventService', () => {
595
- let eventService: EventService;
596
-
597
- beforeEach(() => {
598
- eventService = new EventService();
599
- });
600
-
601
- afterEach(() => {
602
- eventService.removeAllListeners();
603
- });
604
-
605
- describe('user events', () => {
606
- it('should emit user created event', (done) => {
607
- // Arrange
608
- const userData = { id: '123', name: 'John Doe' };
609
-
610
- // Act
611
- eventService.on('user:created', (data) => {
612
- // Assert
613
- expect(data).toEqual(userData);
614
- done();
615
- });
616
-
617
- eventService.createUser(userData);
618
- });
619
-
620
- it('should handle multiple listeners', () => {
621
- // Arrange
622
- const userData = { id: '123', name: 'John Doe' };
623
- const listener1 = jest.fn();
624
- const listener2 = jest.fn();
625
-
626
- eventService.on('user:created', listener1);
627
- eventService.on('user:created', listener2);
628
-
629
- // Act
630
- eventService.createUser(userData);
631
-
632
- // Assert
633
- expect(listener1).toHaveBeenCalledWith(userData);
634
- expect(listener2).toHaveBeenCalledWith(userData);
635
- });
636
- });
637
- });
638
- ```
639
-
640
- ## Performance Testing
641
-
642
- ### Benchmark Testing
643
-
644
- ```typescript
645
- // test/performance/string-utils.perf.test.ts
646
- import { performance } from 'perf_hooks';
647
- import { processLargeString } from '../../src/utils/string-utils';
648
-
649
- describe('String Utils Performance', () => {
650
- const PERFORMANCE_THRESHOLD = 100; // milliseconds
651
-
652
- describe('processLargeString', () => {
653
- it('should process large string within performance threshold', () => {
654
- // Arrange
655
- const largeString = 'x'.repeat(1000000); // 1MB string
656
-
657
- // Act
658
- const startTime = performance.now();
659
- const result = processLargeString(largeString);
660
- const endTime = performance.now();
661
-
662
- // Assert
663
- expect(result).toBeDefined();
664
- expect(endTime - startTime).toBeLessThan(PERFORMANCE_THRESHOLD);
665
- });
666
- });
667
- });
668
- ```
669
-
670
- ## Test Utilities and Helpers
671
-
672
- ### Test Data Factories
673
-
674
- ```typescript
675
- // test/factories/user-factory.ts
676
- import { User } from '../../src/types/user';
677
-
678
- export class UserFactory {
679
- static create(overrides: Partial<User> = {}): User {
680
- return {
681
- id: Math.random().toString(36).substr(2, 9),
682
- name: 'Test User',
683
- email: 'test@example.com',
684
- createdAt: new Date(),
685
- ...overrides,
686
- };
687
- }
688
-
689
- static createMany(count: number, overrides: Partial<User> = {}): User[] {
690
- return Array.from({ length: count }, (_, index) =>
691
- this.create({
692
- ...overrides,
693
- email: `test${index}@example.com`,
694
- })
695
- );
696
- }
697
- }
698
-
699
- // Usage in tests
700
- describe('User Service', () => {
701
- it('should process multiple users', () => {
702
- const users = UserFactory.createMany(5);
703
- expect(users).toHaveLength(5);
704
- expect(users[0].email).toBe('test0@example.com');
705
- });
706
- });
707
- ```
708
-
709
- ### Custom Matchers
710
-
711
- ```typescript
712
- // test/matchers/custom-matchers.ts
713
- export const customMatchers = {
714
- toBeValidUser(received: any) {
715
- const pass =
716
- typeof received === 'object' &&
717
- typeof received.id === 'string' &&
718
- typeof received.name === 'string' &&
719
- typeof received.email === 'string' &&
720
- received.email.includes('@');
721
-
722
- return {
723
- message: () => `Expected ${received} to be a valid user object`,
724
- pass,
725
- };
726
- },
727
- };
728
-
729
- // Setup in test files
730
- expect.extend(customMatchers);
731
-
732
- // Usage
733
- describe('User Service', () => {
734
- it('should return valid user', () => {
735
- const user = userService.getUser('123');
736
- expect(user).toBeValidUser();
737
- });
738
- });
739
- ```