metacoding 1.5.0 → 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.
- package/CHANGELOG.md +59 -0
- package/README.md +108 -514
- package/lib/cli.d.ts.map +1 -1
- package/lib/cli.js +18 -19
- package/lib/cli.js.map +1 -1
- package/lib/commands/init.d.ts +8 -14
- package/lib/commands/init.d.ts.map +1 -1
- package/lib/commands/init.js +105 -387
- package/lib/commands/init.js.map +1 -1
- package/lib/commands/update.d.ts +9 -9
- package/lib/commands/update.d.ts.map +1 -1
- package/lib/commands/update.js +141 -320
- package/lib/commands/update.js.map +1 -1
- package/lib/services/backup.d.ts +1 -1
- package/lib/services/backup.d.ts.map +1 -1
- package/lib/services/backup.js +10 -6
- package/lib/services/backup.js.map +1 -1
- package/lib/services/filesystem.d.ts.map +1 -1
- package/lib/services/filesystem.js +11 -5
- package/lib/services/filesystem.js.map +1 -1
- package/lib/services/gitignore-manager.js +5 -5
- package/lib/services/gitignore-manager.js.map +1 -1
- package/lib/services/project-detector.d.ts +9 -8
- package/lib/services/project-detector.d.ts.map +1 -1
- package/lib/services/project-detector.js +79 -197
- package/lib/services/project-detector.js.map +1 -1
- package/lib/services/skill-manager.d.ts +23 -0
- package/lib/services/skill-manager.d.ts.map +1 -0
- package/lib/services/skill-manager.js +212 -0
- package/lib/services/skill-manager.js.map +1 -0
- package/lib/types/index.d.ts +5 -15
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +9 -17
- package/skills/metacoding-workflow/SKILL.md +52 -0
- package/skills/metacoding-workflow/agents/openai.yaml +4 -0
- package/skills/metacoding-workflow/assets/templates/changelog-entry.md +6 -0
- package/skills/metacoding-workflow/assets/templates/project-context.md +18 -0
- package/skills/metacoding-workflow/assets/templates/repeated-task-checklist.md +8 -0
- package/skills/metacoding-workflow/assets/templates/task-entry.md +9 -0
- package/skills/metacoding-workflow/assets/templates/test-plan.md +8 -0
- package/skills/metacoding-workflow/references/javascript.md +7 -0
- package/skills/metacoding-workflow/references/node.md +7 -0
- package/skills/metacoding-workflow/references/platform-adaptation.md +37 -0
- package/skills/metacoding-workflow/references/python.md +7 -0
- package/skills/metacoding-workflow/references/react.md +7 -0
- package/skills/metacoding-workflow/references/repository-organization.md +84 -0
- package/skills/metacoding-workflow/references/typescript.md +7 -0
- package/skills/metacoding-workflow/references/workflow-rules.md +54 -0
- package/skills/vendor-templates/claude-agent.md.template +41 -0
- package/lib/services/assistant-adapter.d.ts +0 -18
- package/lib/services/assistant-adapter.d.ts.map +0 -1
- package/lib/services/assistant-adapter.js +0 -246
- package/lib/services/assistant-adapter.js.map +0 -1
- package/lib/services/cursor.d.ts +0 -47
- package/lib/services/cursor.d.ts.map +0 -1
- package/lib/services/cursor.js +0 -314
- package/lib/services/cursor.js.map +0 -1
- package/lib/services/template-manager.d.ts +0 -23
- package/lib/services/template-manager.d.ts.map +0 -1
- package/lib/services/template-manager.js +0 -374
- package/lib/services/template-manager.js.map +0 -1
- package/lib/services/vscode.d.ts +0 -10
- package/lib/services/vscode.d.ts.map +0 -1
- package/lib/services/vscode.js +0 -108
- package/lib/services/vscode.js.map +0 -1
- package/templates/assistants/AGENTS.md +0 -203
- package/templates/assistants/CLAUDE.md +0 -156
- package/templates/assistants/GEMINI.md +0 -193
- package/templates/general/code-review.instructions.md +0 -265
- package/templates/general/copilot-instructions.md +0 -427
- package/templates/general/docs-update.instructions.md +0 -275
- package/templates/general/release.instructions.md +0 -242
- package/templates/general/template.json +0 -9
- package/templates/general/test-runner.instructions.md +0 -188
- package/templates/javascript/javascript.coding.instructions.md +0 -500
- package/templates/javascript/javascript.docs.instructions.md +0 -563
- package/templates/javascript/javascript.testing.instructions.md +0 -686
- package/templates/javascript/template.json +0 -36
- package/templates/node/nodejs.coding.instructions.md +0 -249
- package/templates/node/nodejs.docs.instructions.md +0 -261
- package/templates/node/nodejs.testing.instructions.md +0 -373
- package/templates/node/template.json +0 -23
- package/templates/python/python.coding.instructions.md +0 -338
- package/templates/python/python.docs.instructions.md +0 -1178
- package/templates/python/python.testing.instructions.md +0 -1073
- package/templates/python/template.json +0 -75
- package/templates/react/react.coding.instructions.md +0 -694
- package/templates/react/react.docs.instructions.md +0 -451
- package/templates/react/react.testing.instructions.md +0 -192
- package/templates/react/template.json +0 -14
- package/templates/react/test-runner.instructions.md +0 -135
- package/templates/typescript/template.json +0 -16
- package/templates/typescript/typescript.coding.instructions.md +0 -368
- package/templates/typescript/typescript.docs.instructions.md +0 -760
- package/templates/typescript/typescript.testing.instructions.md +0 -739
|
@@ -1,686 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: 'JavaScript testing strategies and frameworks for browser and Node.js environments'
|
|
3
|
-
applyTo: 'test/**/*.{js,mjs}'
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# JavaScript Testing Guidelines
|
|
7
|
-
|
|
8
|
-
## Test Case Naming Conventions
|
|
9
|
-
|
|
10
|
-
### Test Case ID Format: `[AREA]-[TYPE]-[NUMBER]`
|
|
11
|
-
|
|
12
|
-
**JavaScript/Frontend Area Prefixes:**
|
|
13
|
-
|
|
14
|
-
- `DOM` - DOM manipulation and browser API tests
|
|
15
|
-
- `UI` - User interface component tests
|
|
16
|
-
- `EVENT` - Event handling and interaction tests
|
|
17
|
-
- `API_CLIENT` - API client and HTTP request tests
|
|
18
|
-
- `UTIL` - Utility function tests
|
|
19
|
-
- `CORE` - Core application logic tests
|
|
20
|
-
- `MODULE` - Module loading and dependency tests
|
|
21
|
-
- `SERVER` - Server-side JavaScript tests (Node.js)
|
|
22
|
-
- `CLI` - Command-line interface tests (Node.js)
|
|
23
|
-
- `PKG` - Package/library functionality tests
|
|
24
|
-
|
|
25
|
-
**Type Suffixes:**
|
|
26
|
-
|
|
27
|
-
- `UNIT` - Unit tests (isolated function/component testing)
|
|
28
|
-
- `INT` - Integration tests (component interaction testing)
|
|
29
|
-
- `E2E` - End-to-end tests (full user workflow testing)
|
|
30
|
-
|
|
31
|
-
**Examples:**
|
|
32
|
-
|
|
33
|
-
- `DOM-UNIT-001` - First unit test for DOM manipulation
|
|
34
|
-
- `UI-UNIT-001` - First unit test for UI component
|
|
35
|
-
- `API_CLIENT-INT-001` - First integration test for API client
|
|
36
|
-
- `EVENT-E2E-001` - First end-to-end event handling test
|
|
37
|
-
|
|
38
|
-
## Testing Strategy Overview
|
|
39
|
-
|
|
40
|
-
### Testing Pyramid for JavaScript
|
|
41
|
-
|
|
42
|
-
- **Unit Tests (70%):** Test individual functions and modules in isolation
|
|
43
|
-
- **Integration Tests (20%):** Test interactions between components
|
|
44
|
-
- **End-to-End Tests (10%):** Test complete user workflows in browser/environment
|
|
45
|
-
|
|
46
|
-
### Test Framework Selection
|
|
47
|
-
|
|
48
|
-
#### Primary Testing Frameworks
|
|
49
|
-
|
|
50
|
-
- **Jest:** Comprehensive testing framework for unit and integration tests
|
|
51
|
-
- **Vitest:** Fast alternative to Jest with native ES modules support
|
|
52
|
-
- **Mocha + Chai:** Flexible testing framework with assertion library
|
|
53
|
-
- **Cypress/Playwright:** End-to-end testing for browser applications
|
|
54
|
-
|
|
55
|
-
#### Browser Testing Tools
|
|
56
|
-
|
|
57
|
-
- **jsdom:** Simulate browser environment in Node.js for DOM testing
|
|
58
|
-
- **@testing-library/dom:** Simple and complete testing utilities for DOM
|
|
59
|
-
- **Puppeteer:** Control headless Chrome for integration testing
|
|
60
|
-
- **WebDriver:** Cross-browser testing automation
|
|
61
|
-
|
|
62
|
-
## Jest Testing Patterns
|
|
63
|
-
|
|
64
|
-
### Basic Test Structure
|
|
65
|
-
|
|
66
|
-
```javascript
|
|
67
|
-
// Basic test setup
|
|
68
|
-
describe('UserService', () => {
|
|
69
|
-
let userService;
|
|
70
|
-
let mockDatabase;
|
|
71
|
-
|
|
72
|
-
beforeEach(() => {
|
|
73
|
-
mockDatabase = {
|
|
74
|
-
users: {
|
|
75
|
-
findById: jest.fn(),
|
|
76
|
-
create: jest.fn(),
|
|
77
|
-
update: jest.fn(),
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
userService = new UserService(mockDatabase);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
afterEach(() => {
|
|
84
|
-
jest.clearAllMocks();
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
describe('getUserById', () => {
|
|
88
|
-
it('should return user when found', async () => {
|
|
89
|
-
// Arrange
|
|
90
|
-
const userId = '123';
|
|
91
|
-
const expectedUser = { id: userId, name: 'John Doe' };
|
|
92
|
-
mockDatabase.users.findById.mockResolvedValue(expectedUser);
|
|
93
|
-
|
|
94
|
-
// Act
|
|
95
|
-
const result = await userService.getUserById(userId);
|
|
96
|
-
|
|
97
|
-
// Assert
|
|
98
|
-
expect(result).toEqual(expectedUser);
|
|
99
|
-
expect(mockDatabase.users.findById).toHaveBeenCalledWith(userId);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('should return null when user not found', async () => {
|
|
103
|
-
// Arrange
|
|
104
|
-
const userId = '999';
|
|
105
|
-
mockDatabase.users.findById.mockResolvedValue(null);
|
|
106
|
-
|
|
107
|
-
// Act
|
|
108
|
-
const result = await userService.getUserById(userId);
|
|
109
|
-
|
|
110
|
-
// Assert
|
|
111
|
-
expect(result).toBeNull();
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('should handle database errors gracefully', async () => {
|
|
115
|
-
// Arrange
|
|
116
|
-
const userId = '123';
|
|
117
|
-
const dbError = new Error('Database connection failed');
|
|
118
|
-
mockDatabase.users.findById.mockRejectedValue(dbError);
|
|
119
|
-
|
|
120
|
-
// Act
|
|
121
|
-
const result = await userService.getUserById(userId);
|
|
122
|
-
|
|
123
|
-
// Assert
|
|
124
|
-
expect(result).toBeNull();
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Testing Async Code
|
|
131
|
-
|
|
132
|
-
```javascript
|
|
133
|
-
// Testing promises and async/await
|
|
134
|
-
describe('API Client', () => {
|
|
135
|
-
let apiClient;
|
|
136
|
-
let mockFetch;
|
|
137
|
-
|
|
138
|
-
beforeEach(() => {
|
|
139
|
-
mockFetch = jest.fn();
|
|
140
|
-
global.fetch = mockFetch;
|
|
141
|
-
apiClient = new ApiClient('https://api.example.com');
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
afterEach(() => {
|
|
145
|
-
global.fetch.mockRestore();
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('should fetch user data successfully', async () => {
|
|
149
|
-
// Arrange
|
|
150
|
-
const userData = { id: 1, name: 'John' };
|
|
151
|
-
mockFetch.mockResolvedValue({
|
|
152
|
-
ok: true,
|
|
153
|
-
json: jest.fn().mockResolvedValue(userData),
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
// Act
|
|
157
|
-
const result = await apiClient.getUser(1);
|
|
158
|
-
|
|
159
|
-
// Assert
|
|
160
|
-
expect(result).toEqual(userData);
|
|
161
|
-
expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/users/1');
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('should handle HTTP errors', async () => {
|
|
165
|
-
// Arrange
|
|
166
|
-
mockFetch.mockResolvedValue({
|
|
167
|
-
ok: false,
|
|
168
|
-
status: 404,
|
|
169
|
-
statusText: 'Not Found',
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
// Act & Assert
|
|
173
|
-
await expect(apiClient.getUser(999)).rejects.toThrow(
|
|
174
|
-
'HTTP error! status: 404'
|
|
175
|
-
);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('should handle network errors', async () => {
|
|
179
|
-
// Arrange
|
|
180
|
-
const networkError = new Error('Network error');
|
|
181
|
-
mockFetch.mockRejectedValue(networkError);
|
|
182
|
-
|
|
183
|
-
// Act & Assert
|
|
184
|
-
await expect(apiClient.getUser(1)).rejects.toThrow('Network error');
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### Mocking and Spying
|
|
190
|
-
|
|
191
|
-
```javascript
|
|
192
|
-
// Module mocking
|
|
193
|
-
jest.mock('./logger', () => ({
|
|
194
|
-
log: jest.fn(),
|
|
195
|
-
error: jest.fn(),
|
|
196
|
-
}));
|
|
197
|
-
|
|
198
|
-
// Partial mocking
|
|
199
|
-
jest.mock('./config', () => ({
|
|
200
|
-
...jest.requireActual('./config'),
|
|
201
|
-
apiUrl: 'http://test-api.example.com',
|
|
202
|
-
}));
|
|
203
|
-
|
|
204
|
-
// Spy on methods
|
|
205
|
-
describe('Analytics Service', () => {
|
|
206
|
-
it('should track user actions', () => {
|
|
207
|
-
const analytics = new AnalyticsService();
|
|
208
|
-
const trackSpy = jest.spyOn(analytics, 'track');
|
|
209
|
-
|
|
210
|
-
analytics.trackUserLogin('user123');
|
|
211
|
-
|
|
212
|
-
expect(trackSpy).toHaveBeenCalledWith('user_login', { userId: 'user123' });
|
|
213
|
-
|
|
214
|
-
trackSpy.mockRestore();
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
// Timer mocking
|
|
219
|
-
describe('Debounced Function', () => {
|
|
220
|
-
beforeEach(() => {
|
|
221
|
-
jest.useFakeTimers();
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
afterEach(() => {
|
|
225
|
-
jest.runOnlyPendingTimers();
|
|
226
|
-
jest.useRealTimers();
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it('should debounce function calls', () => {
|
|
230
|
-
const mockFn = jest.fn();
|
|
231
|
-
const debouncedFn = debounce(mockFn, 100);
|
|
232
|
-
|
|
233
|
-
debouncedFn();
|
|
234
|
-
debouncedFn();
|
|
235
|
-
debouncedFn();
|
|
236
|
-
|
|
237
|
-
expect(mockFn).not.toHaveBeenCalled();
|
|
238
|
-
|
|
239
|
-
jest.advanceTimersByTime(100);
|
|
240
|
-
|
|
241
|
-
expect(mockFn).toHaveBeenCalledTimes(1);
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
## DOM Testing with jsdom
|
|
247
|
-
|
|
248
|
-
### Testing DOM Manipulation
|
|
249
|
-
|
|
250
|
-
```javascript
|
|
251
|
-
/**
|
|
252
|
-
* @jest-environment jsdom
|
|
253
|
-
*/
|
|
254
|
-
|
|
255
|
-
describe('DOM Utilities', () => {
|
|
256
|
-
beforeEach(() => {
|
|
257
|
-
document.body.innerHTML = '';
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
it('should create and append element', () => {
|
|
261
|
-
// Arrange
|
|
262
|
-
const container = document.createElement('div');
|
|
263
|
-
document.body.appendChild(container);
|
|
264
|
-
|
|
265
|
-
// Act
|
|
266
|
-
const element = createButton('Click me', 'btn-primary');
|
|
267
|
-
container.appendChild(element);
|
|
268
|
-
|
|
269
|
-
// Assert
|
|
270
|
-
expect(container.children).toHaveLength(1);
|
|
271
|
-
expect(container.firstChild.textContent).toBe('Click me');
|
|
272
|
-
expect(container.firstChild.className).toBe('btn-primary');
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it('should handle click events', () => {
|
|
276
|
-
// Arrange
|
|
277
|
-
const mockHandler = jest.fn();
|
|
278
|
-
const button = document.createElement('button');
|
|
279
|
-
button.addEventListener('click', mockHandler);
|
|
280
|
-
document.body.appendChild(button);
|
|
281
|
-
|
|
282
|
-
// Act
|
|
283
|
-
button.click();
|
|
284
|
-
|
|
285
|
-
// Assert
|
|
286
|
-
expect(mockHandler).toHaveBeenCalledTimes(1);
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
it('should update element content', () => {
|
|
290
|
-
// Arrange
|
|
291
|
-
document.body.innerHTML = '<div id="counter">0</div>';
|
|
292
|
-
const counter = document.getElementById('counter');
|
|
293
|
-
|
|
294
|
-
// Act
|
|
295
|
-
updateCounter(counter, 5);
|
|
296
|
-
|
|
297
|
-
// Assert
|
|
298
|
-
expect(counter.textContent).toBe('5');
|
|
299
|
-
});
|
|
300
|
-
});
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
### Testing with @testing-library
|
|
304
|
-
|
|
305
|
-
```javascript
|
|
306
|
-
import { fireEvent, screen } from '@testing-library/dom';
|
|
307
|
-
import '@testing-library/jest-dom';
|
|
308
|
-
|
|
309
|
-
describe('Form Component', () => {
|
|
310
|
-
beforeEach(() => {
|
|
311
|
-
document.body.innerHTML = `
|
|
312
|
-
<form id="login-form">
|
|
313
|
-
<input type="email" id="email" placeholder="Email" />
|
|
314
|
-
<input type="password" id="password" placeholder="Password" />
|
|
315
|
-
<button type="submit">Login</button>
|
|
316
|
-
</form>
|
|
317
|
-
`;
|
|
318
|
-
});
|
|
319
|
-
|
|
320
|
-
it('should validate email input', () => {
|
|
321
|
-
// Arrange
|
|
322
|
-
const emailInput = screen.getByPlaceholderText('Email');
|
|
323
|
-
|
|
324
|
-
// Act
|
|
325
|
-
fireEvent.change(emailInput, { target: { value: 'invalid-email' } });
|
|
326
|
-
fireEvent.blur(emailInput);
|
|
327
|
-
|
|
328
|
-
// Assert
|
|
329
|
-
expect(emailInput).toHaveClass('error');
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
it('should submit form with valid data', () => {
|
|
333
|
-
// Arrange
|
|
334
|
-
const mockSubmit = jest.fn();
|
|
335
|
-
const form = document.getElementById('login-form');
|
|
336
|
-
form.addEventListener('submit', mockSubmit);
|
|
337
|
-
|
|
338
|
-
// Act
|
|
339
|
-
fireEvent.change(screen.getByPlaceholderText('Email'), {
|
|
340
|
-
target: { value: 'user@example.com' },
|
|
341
|
-
});
|
|
342
|
-
fireEvent.change(screen.getByPlaceholderText('Password'), {
|
|
343
|
-
target: { value: 'password123' },
|
|
344
|
-
});
|
|
345
|
-
fireEvent.click(screen.getByText('Login'));
|
|
346
|
-
|
|
347
|
-
// Assert
|
|
348
|
-
expect(mockSubmit).toHaveBeenCalled();
|
|
349
|
-
});
|
|
350
|
-
});
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
## Node.js Testing Patterns
|
|
354
|
-
|
|
355
|
-
### File System Testing
|
|
356
|
-
|
|
357
|
-
```javascript
|
|
358
|
-
import { promises as fs } from 'fs';
|
|
359
|
-
import path from 'path';
|
|
360
|
-
import { tmpdir } from 'os';
|
|
361
|
-
|
|
362
|
-
describe('File Service', () => {
|
|
363
|
-
let tempDir;
|
|
364
|
-
let fileService;
|
|
365
|
-
|
|
366
|
-
beforeEach(async () => {
|
|
367
|
-
tempDir = await fs.mkdtemp(path.join(tmpdir(), 'test-'));
|
|
368
|
-
fileService = new FileService(tempDir);
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
afterEach(async () => {
|
|
372
|
-
await fs.rmdir(tempDir, { recursive: true });
|
|
373
|
-
});
|
|
374
|
-
|
|
375
|
-
it('should create and read file', async () => {
|
|
376
|
-
// Arrange
|
|
377
|
-
const filename = 'test.txt';
|
|
378
|
-
const content = 'Hello, World!';
|
|
379
|
-
|
|
380
|
-
// Act
|
|
381
|
-
await fileService.writeFile(filename, content);
|
|
382
|
-
const result = await fileService.readFile(filename);
|
|
383
|
-
|
|
384
|
-
// Assert
|
|
385
|
-
expect(result).toBe(content);
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
it('should handle file not found error', async () => {
|
|
389
|
-
// Act & Assert
|
|
390
|
-
await expect(fileService.readFile('nonexistent.txt')).rejects.toThrow(
|
|
391
|
-
'File not found'
|
|
392
|
-
);
|
|
393
|
-
});
|
|
394
|
-
});
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
### HTTP Server Testing
|
|
398
|
-
|
|
399
|
-
```javascript
|
|
400
|
-
import request from 'supertest';
|
|
401
|
-
import app from '../src/app.js';
|
|
402
|
-
|
|
403
|
-
describe('API Endpoints', () => {
|
|
404
|
-
describe('GET /api/users', () => {
|
|
405
|
-
it('should return list of users', async () => {
|
|
406
|
-
const response = await request(app)
|
|
407
|
-
.get('/api/users')
|
|
408
|
-
.expect('Content-Type', /json/)
|
|
409
|
-
.expect(200);
|
|
410
|
-
|
|
411
|
-
expect(response.body).toHaveProperty('users');
|
|
412
|
-
expect(Array.isArray(response.body.users)).toBe(true);
|
|
413
|
-
});
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
describe('POST /api/users', () => {
|
|
417
|
-
it('should create new user', async () => {
|
|
418
|
-
const newUser = {
|
|
419
|
-
name: 'John Doe',
|
|
420
|
-
email: 'john@example.com',
|
|
421
|
-
};
|
|
422
|
-
|
|
423
|
-
const response = await request(app)
|
|
424
|
-
.post('/api/users')
|
|
425
|
-
.send(newUser)
|
|
426
|
-
.expect('Content-Type', /json/)
|
|
427
|
-
.expect(201);
|
|
428
|
-
|
|
429
|
-
expect(response.body).toHaveProperty('id');
|
|
430
|
-
expect(response.body.name).toBe(newUser.name);
|
|
431
|
-
expect(response.body.email).toBe(newUser.email);
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
it('should validate required fields', async () => {
|
|
435
|
-
const invalidUser = { name: 'John Doe' }; // Missing email
|
|
436
|
-
|
|
437
|
-
await request(app)
|
|
438
|
-
.post('/api/users')
|
|
439
|
-
.send(invalidUser)
|
|
440
|
-
.expect('Content-Type', /json/)
|
|
441
|
-
.expect(400);
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
});
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
## End-to-End Testing
|
|
448
|
-
|
|
449
|
-
### Cypress Testing Patterns
|
|
450
|
-
|
|
451
|
-
```javascript
|
|
452
|
-
// cypress/integration/user-workflow.spec.js
|
|
453
|
-
describe('User Registration Flow', () => {
|
|
454
|
-
beforeEach(() => {
|
|
455
|
-
cy.visit('/register');
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
it('should complete user registration', () => {
|
|
459
|
-
// Fill out registration form
|
|
460
|
-
cy.get('[data-testid="email-input"]').type('user@example.com');
|
|
461
|
-
cy.get('[data-testid="password-input"]').type('securepassword');
|
|
462
|
-
cy.get('[data-testid="confirm-password-input"]').type('securepassword');
|
|
463
|
-
|
|
464
|
-
// Submit form
|
|
465
|
-
cy.get('[data-testid="register-button"]').click();
|
|
466
|
-
|
|
467
|
-
// Verify success
|
|
468
|
-
cy.url().should('include', '/dashboard');
|
|
469
|
-
cy.get('[data-testid="welcome-message"]').should('contain', 'Welcome');
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
it('should show validation errors for invalid data', () => {
|
|
473
|
-
cy.get('[data-testid="email-input"]').type('invalid-email');
|
|
474
|
-
cy.get('[data-testid="register-button"]').click();
|
|
475
|
-
|
|
476
|
-
cy.get('[data-testid="email-error"]').should(
|
|
477
|
-
'contain',
|
|
478
|
-
'Valid email required'
|
|
479
|
-
);
|
|
480
|
-
});
|
|
481
|
-
});
|
|
482
|
-
```
|
|
483
|
-
|
|
484
|
-
### Playwright Testing Patterns
|
|
485
|
-
|
|
486
|
-
```javascript
|
|
487
|
-
import { test, expect } from '@playwright/test';
|
|
488
|
-
|
|
489
|
-
test.describe('Shopping Cart', () => {
|
|
490
|
-
test.beforeEach(async ({ page }) => {
|
|
491
|
-
await page.goto('/shop');
|
|
492
|
-
});
|
|
493
|
-
|
|
494
|
-
test('should add items to cart', async ({ page }) => {
|
|
495
|
-
// Add item to cart
|
|
496
|
-
await page.click('[data-testid="add-to-cart-123"]');
|
|
497
|
-
|
|
498
|
-
// Verify cart badge
|
|
499
|
-
await expect(page.locator('[data-testid="cart-count"]')).toHaveText('1');
|
|
500
|
-
|
|
501
|
-
// Navigate to cart
|
|
502
|
-
await page.click('[data-testid="cart-link"]');
|
|
503
|
-
|
|
504
|
-
// Verify item in cart
|
|
505
|
-
await expect(page.locator('[data-testid="cart-item-123"]')).toBeVisible();
|
|
506
|
-
});
|
|
507
|
-
|
|
508
|
-
test('should calculate total correctly', async ({ page }) => {
|
|
509
|
-
await page.click('[data-testid="add-to-cart-123"]'); // $10 item
|
|
510
|
-
await page.click('[data-testid="add-to-cart-456"]'); // $15 item
|
|
511
|
-
|
|
512
|
-
await page.click('[data-testid="cart-link"]');
|
|
513
|
-
|
|
514
|
-
await expect(page.locator('[data-testid="cart-total"]')).toHaveText(
|
|
515
|
-
'$25.00'
|
|
516
|
-
);
|
|
517
|
-
});
|
|
518
|
-
});
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
## Test Configuration
|
|
522
|
-
|
|
523
|
-
### Jest Configuration
|
|
524
|
-
|
|
525
|
-
```javascript
|
|
526
|
-
// jest.config.js
|
|
527
|
-
export default {
|
|
528
|
-
testEnvironment: 'node',
|
|
529
|
-
transform: {},
|
|
530
|
-
extensionsToTreatAsEsm: ['.js'],
|
|
531
|
-
globals: {
|
|
532
|
-
'ts-jest': {
|
|
533
|
-
useESM: true,
|
|
534
|
-
},
|
|
535
|
-
},
|
|
536
|
-
moduleNameMapping: {
|
|
537
|
-
'^@/(.*)$': '<rootDir>/src/$1',
|
|
538
|
-
},
|
|
539
|
-
testMatch: ['<rootDir>/test/**/*.test.js', '<rootDir>/src/**/__tests__/*.js'],
|
|
540
|
-
collectCoverageFrom: ['src/**/*.js', '!src/**/*.test.js', '!src/index.js'],
|
|
541
|
-
coverageDirectory: 'coverage',
|
|
542
|
-
coverageReporters: ['text', 'lcov', 'html'],
|
|
543
|
-
setupFilesAfterEnv: ['<rootDir>/test/setup.js'],
|
|
544
|
-
testTimeout: 10000,
|
|
545
|
-
};
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
### Browser Test Configuration
|
|
549
|
-
|
|
550
|
-
```javascript
|
|
551
|
-
// jest.config.browser.js
|
|
552
|
-
export default {
|
|
553
|
-
testEnvironment: 'jsdom',
|
|
554
|
-
setupFilesAfterEnv: [
|
|
555
|
-
'@testing-library/jest-dom',
|
|
556
|
-
'<rootDir>/test/browser-setup.js',
|
|
557
|
-
],
|
|
558
|
-
moduleNameMapping: {
|
|
559
|
-
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
|
|
560
|
-
'\\.(gif|ttf|eot|svg|png)$': '<rootDir>/test/__mocks__/fileMock.js',
|
|
561
|
-
},
|
|
562
|
-
transform: {
|
|
563
|
-
'^.+\\.js$': 'babel-jest',
|
|
564
|
-
},
|
|
565
|
-
};
|
|
566
|
-
```
|
|
567
|
-
|
|
568
|
-
## Test Data Management
|
|
569
|
-
|
|
570
|
-
### Test Fixtures
|
|
571
|
-
|
|
572
|
-
```javascript
|
|
573
|
-
// test/fixtures/users.js
|
|
574
|
-
export const validUser = {
|
|
575
|
-
id: '1',
|
|
576
|
-
name: 'John Doe',
|
|
577
|
-
email: 'john@example.com',
|
|
578
|
-
active: true,
|
|
579
|
-
createdAt: '2023-01-01T00:00:00Z',
|
|
580
|
-
};
|
|
581
|
-
|
|
582
|
-
export const invalidUser = {
|
|
583
|
-
name: '',
|
|
584
|
-
email: 'invalid-email',
|
|
585
|
-
};
|
|
586
|
-
|
|
587
|
-
export const userList = [
|
|
588
|
-
validUser,
|
|
589
|
-
{
|
|
590
|
-
id: '2',
|
|
591
|
-
name: 'Jane Smith',
|
|
592
|
-
email: 'jane@example.com',
|
|
593
|
-
active: false,
|
|
594
|
-
createdAt: '2023-01-02T00:00:00Z',
|
|
595
|
-
},
|
|
596
|
-
];
|
|
597
|
-
|
|
598
|
-
// Test factories
|
|
599
|
-
export function createUser(overrides = {}) {
|
|
600
|
-
return {
|
|
601
|
-
...validUser,
|
|
602
|
-
id: Math.random().toString(36).substr(2, 9),
|
|
603
|
-
...overrides,
|
|
604
|
-
};
|
|
605
|
-
}
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
### Mock Data Generators
|
|
609
|
-
|
|
610
|
-
```javascript
|
|
611
|
-
// test/helpers/generators.js
|
|
612
|
-
export function generateRandomUser() {
|
|
613
|
-
return {
|
|
614
|
-
id: Math.random().toString(36).substr(2, 9),
|
|
615
|
-
name: `User ${Math.floor(Math.random() * 1000)}`,
|
|
616
|
-
email: `user${Math.floor(Math.random() * 1000)}@example.com`,
|
|
617
|
-
active: Math.random() > 0.5,
|
|
618
|
-
createdAt: new Date().toISOString(),
|
|
619
|
-
};
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
export function generateUserList(count = 10) {
|
|
623
|
-
return Array.from({ length: count }, generateRandomUser);
|
|
624
|
-
}
|
|
625
|
-
```
|
|
626
|
-
|
|
627
|
-
## Testing Best Practices
|
|
628
|
-
|
|
629
|
-
### Test Organization
|
|
630
|
-
|
|
631
|
-
- **Describe blocks:** Group related tests logically
|
|
632
|
-
- **Clear test names:** Use descriptive test names that explain the expected behavior
|
|
633
|
-
- **Arrange-Act-Assert:** Structure tests with clear setup, execution, and verification phases
|
|
634
|
-
- **One assertion per test:** Focus each test on a single behavior
|
|
635
|
-
- **Test isolation:** Ensure tests don't depend on each other
|
|
636
|
-
|
|
637
|
-
### Mock Strategy
|
|
638
|
-
|
|
639
|
-
- **Mock external dependencies:** Isolate units under test from external systems
|
|
640
|
-
- **Use real objects when possible:** Prefer real implementations for simple dependencies
|
|
641
|
-
- **Verify interactions:** Test that methods are called with correct parameters
|
|
642
|
-
- **Clean up mocks:** Reset mocks between tests to avoid interference
|
|
643
|
-
|
|
644
|
-
### Coverage Guidelines
|
|
645
|
-
|
|
646
|
-
- **Aim for high coverage:** Target 80%+ line coverage for critical code paths
|
|
647
|
-
- **Quality over quantity:** Focus on meaningful tests rather than coverage metrics
|
|
648
|
-
- **Test edge cases:** Include tests for error conditions and boundary values
|
|
649
|
-
- **Integration testing:** Ensure components work together correctly
|
|
650
|
-
|
|
651
|
-
### Performance Testing
|
|
652
|
-
|
|
653
|
-
```javascript
|
|
654
|
-
// Performance testing examples
|
|
655
|
-
describe('Performance Tests', () => {
|
|
656
|
-
it('should process large dataset efficiently', () => {
|
|
657
|
-
const largeArray = new Array(10000)
|
|
658
|
-
.fill()
|
|
659
|
-
.map((_, i) => ({ id: i, value: i * 2 }));
|
|
660
|
-
|
|
661
|
-
const start = performance.now();
|
|
662
|
-
const result = processLargeDataset(largeArray);
|
|
663
|
-
const end = performance.now();
|
|
664
|
-
|
|
665
|
-
expect(end - start).toBeLessThan(100); // Should complete in under 100ms
|
|
666
|
-
expect(result).toHaveLength(10000);
|
|
667
|
-
});
|
|
668
|
-
|
|
669
|
-
it('should handle memory efficiently', () => {
|
|
670
|
-
const initialMemory = process.memoryUsage().heapUsed;
|
|
671
|
-
|
|
672
|
-
const largeData = createLargeDataStructure();
|
|
673
|
-
processData(largeData);
|
|
674
|
-
|
|
675
|
-
// Force garbage collection if available
|
|
676
|
-
if (global.gc) {
|
|
677
|
-
global.gc();
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
const finalMemory = process.memoryUsage().heapUsed;
|
|
681
|
-
const memoryIncrease = finalMemory - initialMemory;
|
|
682
|
-
|
|
683
|
-
expect(memoryIncrease).toBeLessThan(50 * 1024 * 1024); // Less than 50MB increase
|
|
684
|
-
});
|
|
685
|
-
});
|
|
686
|
-
```
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "javascript",
|
|
3
|
-
"description": "Modern JavaScript development template for browser and Node.js environments with ES6+, testing frameworks, and build tools",
|
|
4
|
-
"prompts": [],
|
|
5
|
-
"vscodeSettings": {
|
|
6
|
-
"github.copilot.chat.codeGeneration.useInstructionFiles": true,
|
|
7
|
-
"chat.promptFiles": true,
|
|
8
|
-
"editor.formatOnSave": true,
|
|
9
|
-
"editor.codeActionsOnSave": {
|
|
10
|
-
"source.organizeImports": true,
|
|
11
|
-
"source.fixAll.eslint": true
|
|
12
|
-
},
|
|
13
|
-
"javascript.preferences.inlayHints.parameterNames.enabled": "all",
|
|
14
|
-
"javascript.preferences.inlayHints.variableTypes.enabled": true,
|
|
15
|
-
"javascript.preferences.inlayHints.functionLikeReturnTypes.enabled": true,
|
|
16
|
-
"files.exclude": {
|
|
17
|
-
"**/node_modules": true,
|
|
18
|
-
"**/dist": true,
|
|
19
|
-
"**/build": true,
|
|
20
|
-
"**/.nyc_output": true,
|
|
21
|
-
"**/coverage": true,
|
|
22
|
-
"**/public/build": true,
|
|
23
|
-
"**/.cache": true
|
|
24
|
-
},
|
|
25
|
-
"search.exclude": {
|
|
26
|
-
"**/node_modules": true,
|
|
27
|
-
"**/dist": true,
|
|
28
|
-
"**/build": true,
|
|
29
|
-
"**/coverage": true
|
|
30
|
-
},
|
|
31
|
-
"javascript.suggest.autoImports": true,
|
|
32
|
-
"javascript.updateImportsOnFileMove.enabled": "always",
|
|
33
|
-
"eslint.enable": true,
|
|
34
|
-
"eslint.format.enable": true
|
|
35
|
-
}
|
|
36
|
-
}
|