@oalacea/daemon 0.5.0 → 0.5.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 (38) hide show
  1. package/CHANGELOG.md +46 -38
  2. package/LICENSE +23 -23
  3. package/README.md +147 -141
  4. package/agents/deps-analyzer.js +366 -366
  5. package/agents/detector.js +570 -570
  6. package/agents/fix-engine.js +305 -305
  7. package/agents/lighthouse-scanner.js +405 -405
  8. package/agents/perf-analyzer.js +294 -294
  9. package/agents/perf-front-analyzer.js +229 -229
  10. package/agents/test-generator.js +387 -387
  11. package/agents/test-runner.js +318 -318
  12. package/bin/Dockerfile +75 -74
  13. package/bin/cli.js +449 -449
  14. package/lib/config.js +250 -250
  15. package/lib/docker.js +207 -207
  16. package/lib/reporter.js +297 -297
  17. package/package.json +34 -34
  18. package/prompts/DEPS_EFFICIENCY.md +558 -558
  19. package/prompts/E2E.md +491 -491
  20. package/prompts/EXECUTE.md +1060 -1060
  21. package/prompts/INTEGRATION_API.md +484 -484
  22. package/prompts/INTEGRATION_DB.md +425 -425
  23. package/prompts/PERF_API.md +433 -433
  24. package/prompts/PERF_DB.md +430 -430
  25. package/prompts/PERF_FRONT.md +357 -357
  26. package/prompts/REMEDIATION.md +482 -482
  27. package/prompts/UNIT.md +260 -260
  28. package/scripts/dev.js +106 -106
  29. package/templates/README.md +38 -38
  30. package/templates/k6/load-test.js +54 -54
  31. package/templates/playwright/e2e.spec.ts +61 -61
  32. package/templates/vitest/angular-component.test.ts +38 -38
  33. package/templates/vitest/api.test.ts +51 -51
  34. package/templates/vitest/component.test.ts +27 -27
  35. package/templates/vitest/hook.test.ts +36 -36
  36. package/templates/vitest/solid-component.test.ts +34 -34
  37. package/templates/vitest/svelte-component.test.ts +33 -33
  38. package/templates/vitest/vue-component.test.ts +39 -39
@@ -1,387 +1,387 @@
1
- /**
2
- * Daemon - Test Generator Agent
3
- *
4
- * Generates test files based on source code analysis.
5
- * Supports unit, integration, and E2E test generation.
6
- */
7
-
8
- const fs = require('fs');
9
- const path = require('path');
10
-
11
- /**
12
- * Generate a unit test for a component
13
- */
14
- function generateComponentTest(componentPath, componentName, content) {
15
- const hasProps = /interface Props|type Props|Props:/.test(content);
16
- const hasOnClick = /onClick|handleClick/.test(content);
17
- const hasDisabled = /disabled|isDisabled/.test(content);
18
- const hasLoading = /loading|isLoading/.test(content);
19
- const hasChildren = /children|{props\.children}/.test(content);
20
- const hasError = /error|isError/.test(content);
21
- const hasVariants = /variant=|variants/.test(content);
22
-
23
- let imports = [
24
- `import { render, screen } from '@testing-library/react';`,
25
- `import { describe, it, expect, vi } from 'vitest';`,
26
- `import { ${componentName} } from '@/components/${componentName}';`,
27
- ];
28
-
29
- let tests = [];
30
-
31
- // Basic render test
32
- tests.push(`
33
- it('should render', () => {
34
- render(<${componentName} />);
35
- expect(screen.getByRole('button')).toBeInTheDocument();
36
- });`);
37
-
38
- // Children test
39
- if (hasChildren) {
40
- tests.push(`
41
- it('should render children', () => {
42
- render(<${componentName}>Test content</${componentName}>);
43
- expect(screen.getByText('Test content')).toBeInTheDocument();
44
- });`);
45
- }
46
-
47
- // Disabled test
48
- if (hasDisabled) {
49
- tests.push(`
50
- it('should be disabled when disabled prop is true', () => {
51
- render(<${componentName} disabled />);
52
- expect(screen.getByRole('button')).toBeDisabled();
53
- });`);
54
- }
55
-
56
- // Loading test
57
- if (hasLoading) {
58
- tests.push(`
59
- it('should show loading state', () => {
60
- render(<${componentName} loading />);
61
- expect(screen.getByTestId(/spinner|loading/i)).toBeInTheDocument();
62
- });`);
63
- }
64
-
65
- // Error test
66
- if (hasError) {
67
- tests.push(`
68
- it('should show error message', () => {
69
- render(<${componentName} error="Something went wrong" />);
70
- expect(screen.getByText('Something went wrong')).toBeInTheDocument();
71
- });`);
72
- }
73
-
74
- // onClick test
75
- if (hasOnClick) {
76
- tests.push(`
77
- it('should call onClick when clicked', async () => {
78
- const handleClick = vi.fn();
79
- render(<${componentName} onClick={handleClick} />);
80
- await screen.getByRole('button').click();
81
- expect(handleClick).toHaveBeenCalledTimes(1);
82
- });`);
83
- }
84
-
85
- // Variant test
86
- if (hasVariants) {
87
- tests.push(`
88
- it('should apply variant class', () => {
89
- render(<${componentName} variant="danger" />);
90
- expect(screen.getByRole('button')).toHaveClass('btn-danger');
91
- });`);
92
- }
93
-
94
- return `${imports.join('\n')}
95
-
96
- describe('${componentName}', () => {${tests.join('\n')}
97
- });
98
- `;
99
- }
100
-
101
- /**
102
- * Generate a unit test for a hook
103
- */
104
- function generateHookTest(hookPath, hookName, content) {
105
- const hasReturn = /return\s*{/.test(content);
106
- const hasState = /useState|useReducer/.test(content);
107
- const hasEffect = /useEffect/.test(content);
108
- const hasCallback = /useCallback/.test(content);
109
- const hasMemo = /useMemo/.test(content);
110
-
111
- let imports = [
112
- `import { renderHook, act, waitFor } from '@testing-library/react';`,
113
- `import { describe, it, expect, vi } from 'vitest';`,
114
- `import { ${hookName} } from '@/hooks/${hookName}';`,
115
- ];
116
-
117
- let tests = [];
118
-
119
- // Basic return test
120
- tests.push(`
121
- it('should return initial state', () => {
122
- const { result } = renderHook(() => ${hookName}());
123
- expect(result.current).toBeDefined();
124
- });`);
125
-
126
- // State update test
127
- if (hasState) {
128
- tests.push(`
129
- it('should update state', async () => {
130
- const { result } = renderHook(() => ${hookName}());
131
-
132
- act(() => {
133
- result.current.setValue('test');
134
- });
135
-
136
- await waitFor(() => {
137
- expect(result.current.value).toBe('test');
138
- });
139
- });`);
140
- }
141
-
142
- // Cleanup test
143
- if (hasEffect) {
144
- tests.push(`
145
- it('should cleanup on unmount', () => {
146
- const cleanup = vi.fn();
147
- const { unmount } = renderHook(() => ${hookName}({ cleanup }));
148
- unmount();
149
- expect(cleanup).toHaveBeenCalled();
150
- });`);
151
- }
152
-
153
- return `${imports.join('\n')}
154
-
155
- describe('${hookName}', () => {${tests.join('\n')}
156
- });
157
- `;
158
- }
159
-
160
- /**
161
- * Generate a unit test for a utility function
162
- */
163
- function generateUtilTest(utilPath, utilName, content) {
164
- const hasParams = /function\s+\w+\s*\(([^)]+)\)|export\s+const\s+\w+\s*=\s*\(([^)]+)\)/.test(content);
165
- const isAsync = /async\s+function|=>\s*async/.test(content);
166
-
167
- let imports = [
168
- `import { describe, it, expect } from 'vitest';`,
169
- `import { ${utilName} } from '@/utils/${utilName}';`,
170
- ];
171
-
172
- let tests = [];
173
-
174
- if (isAsync) {
175
- tests.push(`
176
- it('should return result', async () => {
177
- const result = await ${utilName}('test');
178
- expect(result).toBeDefined();
179
- });`);
180
- } else {
181
- tests.push(`
182
- it('should return result', () => {
183
- const result = ${utilName}('test');
184
- expect(result).toBeDefined();
185
- });`);
186
- }
187
-
188
- return `${imports.join('\n')}
189
-
190
- describe('${utilName}', () => {${tests.join('\n')}
191
- });
192
- `;
193
- }
194
-
195
- /**
196
- * Generate an integration test for an API route
197
- */
198
- function generateApiTest(routePath, routeMethod, content) {
199
- const hasValidation = /zod|validate|schema/.test(content);
200
- const hasAuth = /auth|requireAuth|getSession/.test(content);
201
- const hasDb = /prisma|db\./.test(content);
202
-
203
- let imports = [
204
- `import { describe, it, expect, beforeEach, afterEach } from 'vitest';`,
205
- `import { POST, GET } from '@/app${routePath}/route';`,
206
- ];
207
-
208
- if (hasDb) {
209
- imports.push(`import { db } from '@test/db';`);
210
- }
211
-
212
- let tests = [];
213
-
214
- // Success test
215
- tests.push(`
216
- it('should return 200 on success', async () => {
217
- const request = new Request('http://localhost:3000${routePath}', {
218
- method: '${routeMethod}',
219
- body: JSON.stringify({ test: 'data' }),
220
- });
221
-
222
- const response = await ${routeMethod}(request);
223
- expect(response.status).toBe(200);
224
- });`);
225
-
226
- // Validation test
227
- if (hasValidation) {
228
- tests.push(`
229
- it('should return 400 for invalid data', async () => {
230
- const request = new Request('http://localhost:3000${routePath}', {
231
- method: '${routeMethod}',
232
- body: JSON.stringify({ invalid: 'data' }),
233
- });
234
-
235
- const response = await ${routeMethod}(request);
236
- expect(response.status).toBe(400);
237
- });`);
238
- }
239
-
240
- // Auth test
241
- if (hasAuth) {
242
- tests.push(`
243
- it('should return 401 without auth', async () => {
244
- const request = new Request('http://localhost:3000${routePath}', {
245
- method: '${routeMethod}',
246
- });
247
-
248
- const response = await ${routeMethod}(request);
249
- expect(response.status).toBe(401);
250
- });`);
251
- }
252
-
253
- // DB transaction test
254
- if (hasDb) {
255
- tests = [
256
- `
257
- beforeEach(async () => {
258
- await db.begin();
259
- });`,
260
- `
261
- afterEach(async () => {
262
- await db.rollback();
263
- });`,
264
- ...tests,
265
- ];
266
- }
267
-
268
- return `${imports.join('\n')}
269
-
270
- describe('${routeMethod} ${routePath}', () => {${tests.join('\n')}
271
- });
272
- `;
273
- }
274
-
275
- /**
276
- * Generate an E2E test with Playwright
277
- */
278
- function generateE2ETest(pageName, actions) {
279
- let imports = [
280
- `import { test, expect } from '@playwright/test';`,
281
- ];
282
-
283
- let testContent = '';
284
-
285
- if (actions.includes('login')) {
286
- testContent += `
287
- test.beforeEach(async ({ page }) => {
288
- await page.goto('/login');
289
- });`;
290
- }
291
-
292
- testContent += `
293
-
294
- test('should complete flow', async ({ page }) => {
295
- await page.goto('/${pageName}');`;
296
-
297
- if (actions.includes('fill')) {
298
- testContent += `
299
- await page.fill('input[name="email"]', 'test@example.com');`;
300
- }
301
-
302
- if (actions.includes('click')) {
303
- testContent += `
304
- await page.click('button[type="submit"]');`;
305
- }
306
-
307
- if (actions.includes('navigate')) {
308
- testContent += `
309
- await expect(page).toHaveURL('/dashboard');`;
310
- }
311
-
312
- testContent += `
313
- });`;
314
-
315
- return `${imports.join('\n')}
316
-
317
- test.describe('${pageName}', () => {${testContent}
318
- });
319
- `;
320
- }
321
-
322
- /**
323
- * Main generator function
324
- */
325
- function generateTest(type, filePath, options = {}) {
326
- if (!fs.existsSync(filePath)) {
327
- throw new Error(`File not found: ${filePath}`);
328
- }
329
-
330
- const content = fs.readFileSync(filePath, 'utf-8');
331
- const filename = path.basename(filePath, path.extname(filePath));
332
-
333
- switch (type) {
334
- case 'component':
335
- return generateComponentTest(filePath, filename, content);
336
- case 'hook':
337
- return generateHookTest(filePath, filename, content);
338
- case 'util':
339
- return generateUtilTest(filePath, filename, content);
340
- case 'api':
341
- return generateApiTest(options.route, options.method, content);
342
- case 'e2e':
343
- return generateE2ETest(filename, options.actions || []);
344
- default:
345
- throw new Error(`Unknown test type: ${type}`);
346
- }
347
- }
348
-
349
- /**
350
- * Find files without tests
351
- */
352
- function findFilesWithoutTests(projectDir, pattern) {
353
- const files = [];
354
-
355
- function findInDir(dir) {
356
- const entries = fs.readdirSync(dir, { withFileTypes: true });
357
-
358
- for (const entry of entries) {
359
- const fullPath = path.join(dir, entry.name);
360
-
361
- if (entry.isDirectory()) {
362
- if (['node_modules', '.next', 'dist', 'build', '.git', 'coverage'].includes(entry.name)) {
363
- continue;
364
- }
365
- findInDir(fullPath);
366
- } else if (entry.isFile() && fullPath.match(pattern)) {
367
- const testPath = fullPath.replace(/\.(tsx?)$/, '.test$1');
368
- if (!fs.existsSync(testPath)) {
369
- files.push(fullPath);
370
- }
371
- }
372
- }
373
- }
374
-
375
- findInDir(projectDir);
376
- return files;
377
- }
378
-
379
- module.exports = {
380
- generateTest,
381
- generateComponentTest,
382
- generateHookTest,
383
- generateUtilTest,
384
- generateApiTest,
385
- generateE2ETest,
386
- findFilesWithoutTests,
387
- };
1
+ /**
2
+ * Daemon - Test Generator Agent
3
+ *
4
+ * Generates test files based on source code analysis.
5
+ * Supports unit, integration, and E2E test generation.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ /**
12
+ * Generate a unit test for a component
13
+ */
14
+ function generateComponentTest(componentPath, componentName, content) {
15
+ const hasProps = /interface Props|type Props|Props:/.test(content);
16
+ const hasOnClick = /onClick|handleClick/.test(content);
17
+ const hasDisabled = /disabled|isDisabled/.test(content);
18
+ const hasLoading = /loading|isLoading/.test(content);
19
+ const hasChildren = /children|{props\.children}/.test(content);
20
+ const hasError = /error|isError/.test(content);
21
+ const hasVariants = /variant=|variants/.test(content);
22
+
23
+ let imports = [
24
+ `import { render, screen } from '@testing-library/react';`,
25
+ `import { describe, it, expect, vi } from 'vitest';`,
26
+ `import { ${componentName} } from '@/components/${componentName}';`,
27
+ ];
28
+
29
+ let tests = [];
30
+
31
+ // Basic render test
32
+ tests.push(`
33
+ it('should render', () => {
34
+ render(<${componentName} />);
35
+ expect(screen.getByRole('button')).toBeInTheDocument();
36
+ });`);
37
+
38
+ // Children test
39
+ if (hasChildren) {
40
+ tests.push(`
41
+ it('should render children', () => {
42
+ render(<${componentName}>Test content</${componentName}>);
43
+ expect(screen.getByText('Test content')).toBeInTheDocument();
44
+ });`);
45
+ }
46
+
47
+ // Disabled test
48
+ if (hasDisabled) {
49
+ tests.push(`
50
+ it('should be disabled when disabled prop is true', () => {
51
+ render(<${componentName} disabled />);
52
+ expect(screen.getByRole('button')).toBeDisabled();
53
+ });`);
54
+ }
55
+
56
+ // Loading test
57
+ if (hasLoading) {
58
+ tests.push(`
59
+ it('should show loading state', () => {
60
+ render(<${componentName} loading />);
61
+ expect(screen.getByTestId(/spinner|loading/i)).toBeInTheDocument();
62
+ });`);
63
+ }
64
+
65
+ // Error test
66
+ if (hasError) {
67
+ tests.push(`
68
+ it('should show error message', () => {
69
+ render(<${componentName} error="Something went wrong" />);
70
+ expect(screen.getByText('Something went wrong')).toBeInTheDocument();
71
+ });`);
72
+ }
73
+
74
+ // onClick test
75
+ if (hasOnClick) {
76
+ tests.push(`
77
+ it('should call onClick when clicked', async () => {
78
+ const handleClick = vi.fn();
79
+ render(<${componentName} onClick={handleClick} />);
80
+ await screen.getByRole('button').click();
81
+ expect(handleClick).toHaveBeenCalledTimes(1);
82
+ });`);
83
+ }
84
+
85
+ // Variant test
86
+ if (hasVariants) {
87
+ tests.push(`
88
+ it('should apply variant class', () => {
89
+ render(<${componentName} variant="danger" />);
90
+ expect(screen.getByRole('button')).toHaveClass('btn-danger');
91
+ });`);
92
+ }
93
+
94
+ return `${imports.join('\n')}
95
+
96
+ describe('${componentName}', () => {${tests.join('\n')}
97
+ });
98
+ `;
99
+ }
100
+
101
+ /**
102
+ * Generate a unit test for a hook
103
+ */
104
+ function generateHookTest(hookPath, hookName, content) {
105
+ const hasReturn = /return\s*{/.test(content);
106
+ const hasState = /useState|useReducer/.test(content);
107
+ const hasEffect = /useEffect/.test(content);
108
+ const hasCallback = /useCallback/.test(content);
109
+ const hasMemo = /useMemo/.test(content);
110
+
111
+ let imports = [
112
+ `import { renderHook, act, waitFor } from '@testing-library/react';`,
113
+ `import { describe, it, expect, vi } from 'vitest';`,
114
+ `import { ${hookName} } from '@/hooks/${hookName}';`,
115
+ ];
116
+
117
+ let tests = [];
118
+
119
+ // Basic return test
120
+ tests.push(`
121
+ it('should return initial state', () => {
122
+ const { result } = renderHook(() => ${hookName}());
123
+ expect(result.current).toBeDefined();
124
+ });`);
125
+
126
+ // State update test
127
+ if (hasState) {
128
+ tests.push(`
129
+ it('should update state', async () => {
130
+ const { result } = renderHook(() => ${hookName}());
131
+
132
+ act(() => {
133
+ result.current.setValue('test');
134
+ });
135
+
136
+ await waitFor(() => {
137
+ expect(result.current.value).toBe('test');
138
+ });
139
+ });`);
140
+ }
141
+
142
+ // Cleanup test
143
+ if (hasEffect) {
144
+ tests.push(`
145
+ it('should cleanup on unmount', () => {
146
+ const cleanup = vi.fn();
147
+ const { unmount } = renderHook(() => ${hookName}({ cleanup }));
148
+ unmount();
149
+ expect(cleanup).toHaveBeenCalled();
150
+ });`);
151
+ }
152
+
153
+ return `${imports.join('\n')}
154
+
155
+ describe('${hookName}', () => {${tests.join('\n')}
156
+ });
157
+ `;
158
+ }
159
+
160
+ /**
161
+ * Generate a unit test for a utility function
162
+ */
163
+ function generateUtilTest(utilPath, utilName, content) {
164
+ const hasParams = /function\s+\w+\s*\(([^)]+)\)|export\s+const\s+\w+\s*=\s*\(([^)]+)\)/.test(content);
165
+ const isAsync = /async\s+function|=>\s*async/.test(content);
166
+
167
+ let imports = [
168
+ `import { describe, it, expect } from 'vitest';`,
169
+ `import { ${utilName} } from '@/utils/${utilName}';`,
170
+ ];
171
+
172
+ let tests = [];
173
+
174
+ if (isAsync) {
175
+ tests.push(`
176
+ it('should return result', async () => {
177
+ const result = await ${utilName}('test');
178
+ expect(result).toBeDefined();
179
+ });`);
180
+ } else {
181
+ tests.push(`
182
+ it('should return result', () => {
183
+ const result = ${utilName}('test');
184
+ expect(result).toBeDefined();
185
+ });`);
186
+ }
187
+
188
+ return `${imports.join('\n')}
189
+
190
+ describe('${utilName}', () => {${tests.join('\n')}
191
+ });
192
+ `;
193
+ }
194
+
195
+ /**
196
+ * Generate an integration test for an API route
197
+ */
198
+ function generateApiTest(routePath, routeMethod, content) {
199
+ const hasValidation = /zod|validate|schema/.test(content);
200
+ const hasAuth = /auth|requireAuth|getSession/.test(content);
201
+ const hasDb = /prisma|db\./.test(content);
202
+
203
+ let imports = [
204
+ `import { describe, it, expect, beforeEach, afterEach } from 'vitest';`,
205
+ `import { POST, GET } from '@/app${routePath}/route';`,
206
+ ];
207
+
208
+ if (hasDb) {
209
+ imports.push(`import { db } from '@test/db';`);
210
+ }
211
+
212
+ let tests = [];
213
+
214
+ // Success test
215
+ tests.push(`
216
+ it('should return 200 on success', async () => {
217
+ const request = new Request('http://localhost:3000${routePath}', {
218
+ method: '${routeMethod}',
219
+ body: JSON.stringify({ test: 'data' }),
220
+ });
221
+
222
+ const response = await ${routeMethod}(request);
223
+ expect(response.status).toBe(200);
224
+ });`);
225
+
226
+ // Validation test
227
+ if (hasValidation) {
228
+ tests.push(`
229
+ it('should return 400 for invalid data', async () => {
230
+ const request = new Request('http://localhost:3000${routePath}', {
231
+ method: '${routeMethod}',
232
+ body: JSON.stringify({ invalid: 'data' }),
233
+ });
234
+
235
+ const response = await ${routeMethod}(request);
236
+ expect(response.status).toBe(400);
237
+ });`);
238
+ }
239
+
240
+ // Auth test
241
+ if (hasAuth) {
242
+ tests.push(`
243
+ it('should return 401 without auth', async () => {
244
+ const request = new Request('http://localhost:3000${routePath}', {
245
+ method: '${routeMethod}',
246
+ });
247
+
248
+ const response = await ${routeMethod}(request);
249
+ expect(response.status).toBe(401);
250
+ });`);
251
+ }
252
+
253
+ // DB transaction test
254
+ if (hasDb) {
255
+ tests = [
256
+ `
257
+ beforeEach(async () => {
258
+ await db.begin();
259
+ });`,
260
+ `
261
+ afterEach(async () => {
262
+ await db.rollback();
263
+ });`,
264
+ ...tests,
265
+ ];
266
+ }
267
+
268
+ return `${imports.join('\n')}
269
+
270
+ describe('${routeMethod} ${routePath}', () => {${tests.join('\n')}
271
+ });
272
+ `;
273
+ }
274
+
275
+ /**
276
+ * Generate an E2E test with Playwright
277
+ */
278
+ function generateE2ETest(pageName, actions) {
279
+ let imports = [
280
+ `import { test, expect } from '@playwright/test';`,
281
+ ];
282
+
283
+ let testContent = '';
284
+
285
+ if (actions.includes('login')) {
286
+ testContent += `
287
+ test.beforeEach(async ({ page }) => {
288
+ await page.goto('/login');
289
+ });`;
290
+ }
291
+
292
+ testContent += `
293
+
294
+ test('should complete flow', async ({ page }) => {
295
+ await page.goto('/${pageName}');`;
296
+
297
+ if (actions.includes('fill')) {
298
+ testContent += `
299
+ await page.fill('input[name="email"]', 'test@example.com');`;
300
+ }
301
+
302
+ if (actions.includes('click')) {
303
+ testContent += `
304
+ await page.click('button[type="submit"]');`;
305
+ }
306
+
307
+ if (actions.includes('navigate')) {
308
+ testContent += `
309
+ await expect(page).toHaveURL('/dashboard');`;
310
+ }
311
+
312
+ testContent += `
313
+ });`;
314
+
315
+ return `${imports.join('\n')}
316
+
317
+ test.describe('${pageName}', () => {${testContent}
318
+ });
319
+ `;
320
+ }
321
+
322
+ /**
323
+ * Main generator function
324
+ */
325
+ function generateTest(type, filePath, options = {}) {
326
+ if (!fs.existsSync(filePath)) {
327
+ throw new Error(`File not found: ${filePath}`);
328
+ }
329
+
330
+ const content = fs.readFileSync(filePath, 'utf-8');
331
+ const filename = path.basename(filePath, path.extname(filePath));
332
+
333
+ switch (type) {
334
+ case 'component':
335
+ return generateComponentTest(filePath, filename, content);
336
+ case 'hook':
337
+ return generateHookTest(filePath, filename, content);
338
+ case 'util':
339
+ return generateUtilTest(filePath, filename, content);
340
+ case 'api':
341
+ return generateApiTest(options.route, options.method, content);
342
+ case 'e2e':
343
+ return generateE2ETest(filename, options.actions || []);
344
+ default:
345
+ throw new Error(`Unknown test type: ${type}`);
346
+ }
347
+ }
348
+
349
+ /**
350
+ * Find files without tests
351
+ */
352
+ function findFilesWithoutTests(projectDir, pattern) {
353
+ const files = [];
354
+
355
+ function findInDir(dir) {
356
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
357
+
358
+ for (const entry of entries) {
359
+ const fullPath = path.join(dir, entry.name);
360
+
361
+ if (entry.isDirectory()) {
362
+ if (['node_modules', '.next', 'dist', 'build', '.git', 'coverage'].includes(entry.name)) {
363
+ continue;
364
+ }
365
+ findInDir(fullPath);
366
+ } else if (entry.isFile() && fullPath.match(pattern)) {
367
+ const testPath = fullPath.replace(/\.(tsx?)$/, '.test$1');
368
+ if (!fs.existsSync(testPath)) {
369
+ files.push(fullPath);
370
+ }
371
+ }
372
+ }
373
+ }
374
+
375
+ findInDir(projectDir);
376
+ return files;
377
+ }
378
+
379
+ module.exports = {
380
+ generateTest,
381
+ generateComponentTest,
382
+ generateHookTest,
383
+ generateUtilTest,
384
+ generateApiTest,
385
+ generateE2ETest,
386
+ findFilesWithoutTests,
387
+ };