@oalacea/demon 1.0.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.
- package/CHANGELOG.md +38 -0
- package/LICENSE +23 -0
- package/README.md +103 -0
- package/agents/deps-analyzer.js +366 -0
- package/agents/detector.js +570 -0
- package/agents/fix-engine.js +305 -0
- package/agents/perf-analyzer.js +294 -0
- package/agents/test-generator.js +387 -0
- package/agents/test-runner.js +318 -0
- package/bin/Dockerfile +65 -0
- package/bin/cli.js +455 -0
- package/lib/config.js +237 -0
- package/lib/docker.js +207 -0
- package/lib/reporter.js +297 -0
- package/package.json +34 -0
- package/prompts/DEPS_EFFICIENCY.md +558 -0
- package/prompts/E2E.md +491 -0
- package/prompts/EXECUTE.md +782 -0
- package/prompts/INTEGRATION_API.md +484 -0
- package/prompts/INTEGRATION_DB.md +425 -0
- package/prompts/PERF_API.md +433 -0
- package/prompts/PERF_DB.md +430 -0
- package/prompts/REMEDIATION.md +482 -0
- package/prompts/UNIT.md +260 -0
- package/scripts/dev.js +106 -0
- package/templates/README.md +22 -0
- package/templates/k6/load-test.js +54 -0
- package/templates/playwright/e2e.spec.ts +61 -0
- package/templates/vitest/api.test.ts +51 -0
- package/templates/vitest/component.test.ts +27 -0
- package/templates/vitest/hook.test.ts +36 -0
package/prompts/UNIT.md
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Unit Test Generation Guide
|
|
2
|
+
|
|
3
|
+
This prompt is included by EXECUTE.md. It provides detailed guidance for generating unit tests.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Component Test Patterns
|
|
8
|
+
|
|
9
|
+
### Presentational Components
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// Button component test
|
|
13
|
+
describe('Button', () => {
|
|
14
|
+
it('should render with default props', () => {
|
|
15
|
+
render(<Button>Click me</Button>);
|
|
16
|
+
expect(screen.getByRole('button')).toHaveTextContent('Click me');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('should apply variant classes', () => {
|
|
20
|
+
render(<Button variant="danger">Delete</Button>);
|
|
21
|
+
expect(screen.getByRole('button')).toHaveClass('btn-danger');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should be disabled when loading', () => {
|
|
25
|
+
render(<Button loading>Submit</Button>);
|
|
26
|
+
expect(screen.getByRole('button')).toBeDisabled();
|
|
27
|
+
expect(screen.getByTestId('spinner')).toBeInTheDocument();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Container Components
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// UserList component test
|
|
36
|
+
describe('UserList', () => {
|
|
37
|
+
it('should show loading state', () => {
|
|
38
|
+
vi.mocked(useUsers).mockReturnValue({ data: null, loading: true });
|
|
39
|
+
render(<UserList />);
|
|
40
|
+
expect(screen.getByTestId('skeleton')).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should show error state', () => {
|
|
44
|
+
vi.mocked(useUsers).mockReturnValue({
|
|
45
|
+
data: null,
|
|
46
|
+
loading: false,
|
|
47
|
+
error: new Error('Failed to fetch')
|
|
48
|
+
});
|
|
49
|
+
render(<UserList />);
|
|
50
|
+
expect(screen.getByText('Failed to fetch')).toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should render users', () => {
|
|
54
|
+
vi.mocked(useUsers).mockReturnValue({
|
|
55
|
+
data: [{ id: '1', name: 'John' }],
|
|
56
|
+
loading: false,
|
|
57
|
+
error: null
|
|
58
|
+
});
|
|
59
|
+
render(<UserList />);
|
|
60
|
+
expect(screen.getByText('John')).toBeInTheDocument();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Hook Test Patterns
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { renderHook, act, waitFor } from '@testing-library/react';
|
|
71
|
+
|
|
72
|
+
describe('useDebounce', () => {
|
|
73
|
+
vi.useFakeTimers();
|
|
74
|
+
|
|
75
|
+
it('should debounce value changes', async () => {
|
|
76
|
+
const { result } = renderHook(() => useDebounce('test', 500));
|
|
77
|
+
|
|
78
|
+
// Immediate value
|
|
79
|
+
expect(result.current).toBe('test');
|
|
80
|
+
|
|
81
|
+
// Change value immediately
|
|
82
|
+
act(() => {
|
|
83
|
+
result.current.setValue('test2');
|
|
84
|
+
});
|
|
85
|
+
expect(result.current.value).toBe('test'); // Not debounced yet
|
|
86
|
+
|
|
87
|
+
// Fast-forward
|
|
88
|
+
act(() => {
|
|
89
|
+
vi.advanceTimersByTime(500);
|
|
90
|
+
});
|
|
91
|
+
expect(result.current.value).toBe('test2');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Utility Function Patterns
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
describe('formatCurrency', () => {
|
|
102
|
+
it('should format positive numbers', () => {
|
|
103
|
+
expect(formatCurrency(1234.56)).toBe('$1,234.56');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should format zero', () => {
|
|
107
|
+
expect(formatCurrency(0)).toBe('$0.00');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should handle negative numbers', () => {
|
|
111
|
+
expect(formatCurrency(-100)).toBe('-$100.00');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should round to 2 decimal places', () => {
|
|
115
|
+
expect(formatCurrency(1.999)).toBe('$2.00');
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Validator Test Patterns (Zod)
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { z } from 'zod';
|
|
126
|
+
import { userSchema } from './schema';
|
|
127
|
+
|
|
128
|
+
describe('userSchema', () => {
|
|
129
|
+
const validData = {
|
|
130
|
+
email: 'test@example.com',
|
|
131
|
+
age: 25,
|
|
132
|
+
name: 'John Doe'
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
it('should accept valid data', () => {
|
|
136
|
+
expect(() => userSchema.parse(validData)).not.toThrow();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should reject invalid email', () => {
|
|
140
|
+
expect(() =>
|
|
141
|
+
userSchema.parse({ ...validData, email: 'invalid' })
|
|
142
|
+
).toThrow(z.ZodError);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should reject negative age', () => {
|
|
146
|
+
expect(() =>
|
|
147
|
+
userSchema.parse({ ...validData, age: -1 })
|
|
148
|
+
).toThrow(z.ZodError);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should reject missing required fields', () => {
|
|
152
|
+
expect(() => userSchema.parse({})).toThrow(z.ZodError);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Store Test Patterns (Zustand)
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { create } from 'zustand';
|
|
163
|
+
import { useAuthStore } from './authStore';
|
|
164
|
+
|
|
165
|
+
describe('useAuthStore', () => {
|
|
166
|
+
beforeEach(() => {
|
|
167
|
+
useAuthStore.getState().reset();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should set user on login', () => {
|
|
171
|
+
const user = { id: '1', name: 'John' };
|
|
172
|
+
useAuthStore.getState().login(user);
|
|
173
|
+
expect(useAuthStore.getState().user).toEqual(user);
|
|
174
|
+
expect(useAuthStore.getState().isAuthenticated).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should clear user on logout', () => {
|
|
178
|
+
useAuthStore.getState().login({ id: '1', name: 'John' });
|
|
179
|
+
useAuthStore.getState().logout();
|
|
180
|
+
expect(useAuthStore.getState().user).toBeNull();
|
|
181
|
+
expect(useAuthStore.getState().isAuthenticated).toBe(false);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Mock Patterns
|
|
189
|
+
|
|
190
|
+
### Mocking React Query
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
194
|
+
|
|
195
|
+
function createTestQueryClient() {
|
|
196
|
+
return new QueryClient({
|
|
197
|
+
defaultOptions: {
|
|
198
|
+
queries: { retry: false },
|
|
199
|
+
mutations: { retry: false }
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function Wrapper({ children }) {
|
|
205
|
+
return (
|
|
206
|
+
<QueryClientProvider client={createTestQueryClient()}>
|
|
207
|
+
{children}
|
|
208
|
+
</QueryClientProvider>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
describe('MyComponent', () => {
|
|
213
|
+
it('should work with React Query', () => {
|
|
214
|
+
render(<MyComponent />, { wrapper: Wrapper });
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Mocking Next.js Router
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
import { useRouter } from 'next/navigation';
|
|
223
|
+
|
|
224
|
+
vi.mock('next/navigation', () => ({
|
|
225
|
+
useRouter: () => ({
|
|
226
|
+
push: vi.fn(),
|
|
227
|
+
replace: vi.fn(),
|
|
228
|
+
prefetch: vi.fn(),
|
|
229
|
+
back: vi.fn(),
|
|
230
|
+
pathname: '/test'
|
|
231
|
+
}),
|
|
232
|
+
useSearchParams: () => new URLSearchParams('foo=bar'),
|
|
233
|
+
usePathname: () => '/test'
|
|
234
|
+
}));
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Mocking Fetch
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
global.fetch = vi.fn(() =>
|
|
241
|
+
Promise.resolve({
|
|
242
|
+
ok: true,
|
|
243
|
+
json: () => Promise.resolve({ data: 'test' })
|
|
244
|
+
})
|
|
245
|
+
) as ReturnType<typeof vi.fn>;
|
|
246
|
+
|
|
247
|
+
// Or use MSW for more complex scenarios
|
|
248
|
+
import { http, HttpResponse } from 'msw';
|
|
249
|
+
import { setupServer } from 'msw/node';
|
|
250
|
+
|
|
251
|
+
const server = setupServer(
|
|
252
|
+
http.get('/api/users', () => {
|
|
253
|
+
return HttpResponse.json({ users: [] });
|
|
254
|
+
})
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
beforeAll(() => server.listen());
|
|
258
|
+
afterEach(() => server.resetHandlers());
|
|
259
|
+
afterAll(() => server.close());
|
|
260
|
+
```
|
package/scripts/dev.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Demon Development Script
|
|
5
|
+
*
|
|
6
|
+
* For local development and testing of the Demon CLI.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { execSync } = require('child_process');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const command = process.argv[2] || 'help';
|
|
13
|
+
|
|
14
|
+
const commands = {
|
|
15
|
+
help: () => {
|
|
16
|
+
console.log(`
|
|
17
|
+
Demon Development Commands
|
|
18
|
+
|
|
19
|
+
Usage: node scripts/dev.js <command>
|
|
20
|
+
|
|
21
|
+
Commands:
|
|
22
|
+
test Run detector test
|
|
23
|
+
build Build Docker image
|
|
24
|
+
start Start container
|
|
25
|
+
stop Stop container
|
|
26
|
+
clean Remove container and image
|
|
27
|
+
logs Show container logs
|
|
28
|
+
shell Open shell in container
|
|
29
|
+
link Link package globally for testing
|
|
30
|
+
`);
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
test: () => {
|
|
34
|
+
console.log('Testing detector...');
|
|
35
|
+
const detector = require('../agents/detector.js');
|
|
36
|
+
detector.analyze(process.cwd())
|
|
37
|
+
.then((context) => {
|
|
38
|
+
console.log(JSON.stringify(context, null, 2));
|
|
39
|
+
})
|
|
40
|
+
.catch((err) => {
|
|
41
|
+
console.error('Detection failed:', err);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
build: () => {
|
|
47
|
+
console.log('Building Docker image...');
|
|
48
|
+
const dockerfile = path.join(__dirname, '..', 'bin', 'Dockerfile');
|
|
49
|
+
execSync(`docker build -t demon-tools -f "${dockerfile}" "${path.dirname(dockerfile)}"`, {
|
|
50
|
+
stdio: 'inherit',
|
|
51
|
+
});
|
|
52
|
+
console.log('✓ Build complete');
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
start: () => {
|
|
56
|
+
console.log('Starting container...');
|
|
57
|
+
try {
|
|
58
|
+
execSync('docker start demon-tools', { stdio: 'inherit' });
|
|
59
|
+
console.log('✓ Container started');
|
|
60
|
+
} catch {
|
|
61
|
+
const isLinux = process.platform === 'linux';
|
|
62
|
+
const networkFlag = isLinux ? '--network=host' : '';
|
|
63
|
+
execSync(`docker run -d --name demon-tools ${networkFlag} demon-tools`, {
|
|
64
|
+
stdio: 'inherit',
|
|
65
|
+
});
|
|
66
|
+
console.log('✓ Container created');
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
stop: () => {
|
|
71
|
+
console.log('Stopping container...');
|
|
72
|
+
execSync('docker stop demon-tools', { stdio: 'inherit' });
|
|
73
|
+
console.log('✓ Container stopped');
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
clean: () => {
|
|
77
|
+
console.log('Removing container...');
|
|
78
|
+
execSync('docker rm -f demon-tools', { stdio: 'inherit', timeout: 5000 }).catch(() => {});
|
|
79
|
+
console.log('Removing image...');
|
|
80
|
+
execSync('docker rmi demon-tools', { stdio: 'inherit', timeout: 30000 }).catch(() => {});
|
|
81
|
+
console.log('✓ Clean complete');
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
logs: () => {
|
|
85
|
+
execSync('docker logs -f demon-tools', { stdio: 'inherit' });
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
shell: () => {
|
|
89
|
+
console.log('Opening shell in container...');
|
|
90
|
+
execSync('docker exec -it demon-tools bash', { stdio: 'inherit' });
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
link: () => {
|
|
94
|
+
console.log('Linking package globally...');
|
|
95
|
+
execSync('npm link', { stdio: 'inherit', cwd: path.join(__dirname, '..') });
|
|
96
|
+
console.log('✓ Package linked globally');
|
|
97
|
+
console.log('You can now run: demon');
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (commands[command]) {
|
|
102
|
+
commands[command]();
|
|
103
|
+
} else {
|
|
104
|
+
console.log(`Unknown command: ${command}`);
|
|
105
|
+
commands.help();
|
|
106
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Demon Templates
|
|
2
|
+
|
|
3
|
+
This directory contains test templates for different frameworks and tools.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
templates/
|
|
9
|
+
├── vitest/ # Vitest test templates
|
|
10
|
+
│ ├── component.test.ts
|
|
11
|
+
│ ├── hook.test.ts
|
|
12
|
+
│ └── api.test.ts
|
|
13
|
+
├── playwright/ # Playwright E2E templates
|
|
14
|
+
│ ├── auth.spec.ts
|
|
15
|
+
│ └── crud.spec.ts
|
|
16
|
+
└── k6/ # k6 performance templates
|
|
17
|
+
└── load-test.js
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
Templates are used by the test generator agent to create new test files based on the detected framework and patterns.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import http from 'k6/http';
|
|
2
|
+
import { check, sleep } from 'k6';
|
|
3
|
+
|
|
4
|
+
// Configuration
|
|
5
|
+
export const options = {
|
|
6
|
+
stages: [
|
|
7
|
+
{ duration: '30s', target: 20 }, // Ramp up to 20 users
|
|
8
|
+
{ duration: '1m', target: 20 }, // Stay at 20 users
|
|
9
|
+
{ duration: '30s', target: 50 }, // Ramp up to 50 users
|
|
10
|
+
{ duration: '1m', target: 50 }, // Stay at 50 users
|
|
11
|
+
{ duration: '30s', target: 0 }, // Ramp down
|
|
12
|
+
],
|
|
13
|
+
thresholds: {
|
|
14
|
+
http_req_duration: ['p(95)<200', 'p(99)<500'],
|
|
15
|
+
http_req_failed: ['rate<0.01'],
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const BASE_URL = __ENV.BASE_URL || 'http://host.docker.internal:3000';
|
|
20
|
+
|
|
21
|
+
export default function () {
|
|
22
|
+
// Test homepage
|
|
23
|
+
let res = http.get(`${BASE_URL}/`);
|
|
24
|
+
check(res, {
|
|
25
|
+
'homepage status 200': (r) => r.status === 200,
|
|
26
|
+
'homepage response time < 200ms': (r) => r.timings.duration < 200,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
sleep(1);
|
|
30
|
+
|
|
31
|
+
// Test API endpoint
|
|
32
|
+
res = http.get(`${BASE_URL}/api/users`);
|
|
33
|
+
check(res, {
|
|
34
|
+
'users API status 200': (r) => r.status === 200,
|
|
35
|
+
'users response time < 200ms': (r) => r.timings.duration < 200,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
sleep(1);
|
|
39
|
+
|
|
40
|
+
// Test POST endpoint
|
|
41
|
+
res = http.post(`${BASE_URL}/api/contact`, JSON.stringify({
|
|
42
|
+
name: 'Test User',
|
|
43
|
+
email: 'test@example.com',
|
|
44
|
+
message: 'Test message',
|
|
45
|
+
}), {
|
|
46
|
+
headers: { 'Content-Type': 'application/json' },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
check(res, {
|
|
50
|
+
'contact API status 200': (r) => r.status === 200,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
sleep(1);
|
|
54
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
test.describe('Authentication Flow', () => {
|
|
4
|
+
test.beforeEach(async ({ page }) => {
|
|
5
|
+
await page.goto('/login');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
test('should login with valid credentials', async ({ page }) => {
|
|
9
|
+
await page.fill('input[name="email"]', 'test@example.com');
|
|
10
|
+
await page.fill('input[name="password"]', 'password123');
|
|
11
|
+
await page.click('button[type="submit"]');
|
|
12
|
+
|
|
13
|
+
// Should redirect to dashboard
|
|
14
|
+
await expect(page).toHaveURL(/\/dashboard/);
|
|
15
|
+
await expect(page.locator('h1')).toContainText('Dashboard');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('should show error with invalid credentials', async ({ page }) => {
|
|
19
|
+
await page.fill('input[name="email"]', 'test@example.com');
|
|
20
|
+
await page.fill('input[name="password"]', 'wrong-password');
|
|
21
|
+
await page.click('button[type="submit"]');
|
|
22
|
+
|
|
23
|
+
await expect(page.locator('.error')).toContainText('Invalid credentials');
|
|
24
|
+
await expect(page).toHaveURL('/login');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test.describe('Navigation', () => {
|
|
29
|
+
test('should navigate between pages', async ({ page }) => {
|
|
30
|
+
await page.goto('/');
|
|
31
|
+
|
|
32
|
+
await page.click('text=About');
|
|
33
|
+
await expect(page).toHaveURL('/about');
|
|
34
|
+
|
|
35
|
+
await page.goBack();
|
|
36
|
+
await expect(page).toHaveURL('/');
|
|
37
|
+
|
|
38
|
+
await page.goForward();
|
|
39
|
+
await expect(page).toHaveURL('/about');
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test.describe('Form Interaction', () => {
|
|
44
|
+
test('should submit form successfully', async ({ page }) => {
|
|
45
|
+
await page.goto('/form');
|
|
46
|
+
|
|
47
|
+
await page.fill('input[name="name"]', 'Test User');
|
|
48
|
+
await page.fill('input[name="email"]', 'test@example.com');
|
|
49
|
+
await page.click('button[type="submit"]');
|
|
50
|
+
|
|
51
|
+
await expect(page.locator('.success')).toContainText('Form submitted');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('should show validation errors', async ({ page }) => {
|
|
55
|
+
await page.goto('/form');
|
|
56
|
+
await page.click('button[type="submit"]');
|
|
57
|
+
|
|
58
|
+
await expect(page.locator('input[name="name"]')).toHaveAttribute('aria-invalid', 'true');
|
|
59
|
+
await expect(page.locator('input[name="email"]')).toHaveAttribute('aria-invalid', 'true');
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { POST, GET, PATCH, DELETE } from '@/app/api/route';
|
|
3
|
+
|
|
4
|
+
// TODO: Import database setup if needed
|
|
5
|
+
// import { db } from '@test/db';
|
|
6
|
+
|
|
7
|
+
describe('API Endpoint', () => {
|
|
8
|
+
beforeEach(async () => {
|
|
9
|
+
// TODO: Setup test database
|
|
10
|
+
// await db.begin();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
afterEach(async () => {
|
|
14
|
+
// TODO: Cleanup test database
|
|
15
|
+
// await db.rollback();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should return 200 for GET request', async () => {
|
|
19
|
+
const request = new Request('http://localhost:3000/api/endpoint', {
|
|
20
|
+
method: 'GET',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const response = await GET(request);
|
|
24
|
+
expect(response.status).toBe(200);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should create resource with POST', async () => {
|
|
28
|
+
const request = new Request('http://localhost:3000/api/endpoint', {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: { 'Content-Type': 'application/json' },
|
|
31
|
+
body: JSON.stringify({ name: 'Test' }),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const response = await POST(request);
|
|
35
|
+
expect(response.status).toBe(201);
|
|
36
|
+
|
|
37
|
+
const data = await response.json();
|
|
38
|
+
expect(data).toHaveProperty('id');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should validate input data', async () => {
|
|
42
|
+
const request = new Request('http://localhost:3000/api/endpoint', {
|
|
43
|
+
method: 'POST',
|
|
44
|
+
headers: { 'Content-Type': 'application/json' },
|
|
45
|
+
body: JSON.stringify({ invalid: 'data' }),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const response = await POST(request);
|
|
49
|
+
expect(response.status).toBe(400);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
|
|
5
|
+
// TODO: Import your component
|
|
6
|
+
// import { ComponentName } from '@/components/ComponentName';
|
|
7
|
+
|
|
8
|
+
describe('ComponentName', () => {
|
|
9
|
+
it('should render', () => {
|
|
10
|
+
// TODO: Add component render
|
|
11
|
+
// render(<ComponentName />);
|
|
12
|
+
// expect(screen.getByRole('button')).toBeInTheDocument();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should render with children', () => {
|
|
16
|
+
// TODO: Test children rendering
|
|
17
|
+
// render(<ComponentName>Test content</ComponentName>);
|
|
18
|
+
// expect(screen.getByText('Test content')).toBeInTheDocument();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should handle user interaction', async () => {
|
|
22
|
+
// TODO: Test click/hover/etc
|
|
23
|
+
// const user = userEvent.setup();
|
|
24
|
+
// render(<ComponentName onClick={vi.fn()} />);
|
|
25
|
+
// await user.click(screen.getByRole('button'));
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { renderHook, act, waitFor } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
+
|
|
4
|
+
// TODO: Import your hook
|
|
5
|
+
// import { useHookName } from '@/hooks/useHookName';
|
|
6
|
+
|
|
7
|
+
describe('useHookName', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
vi.clearAllMocks();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should return initial state', () => {
|
|
13
|
+
// TODO: Test initial state
|
|
14
|
+
// const { result } = renderHook(() => useHookName());
|
|
15
|
+
// expect(result.current).toBeDefined();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should update state', async () => {
|
|
19
|
+
// TODO: Test state updates
|
|
20
|
+
// const { result } = renderHook(() => useHookName());
|
|
21
|
+
// act(() => {
|
|
22
|
+
// result.current.setValue('test');
|
|
23
|
+
// });
|
|
24
|
+
// await waitFor(() => {
|
|
25
|
+
// expect(result.current.value).toBe('test');
|
|
26
|
+
// });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should cleanup on unmount', () => {
|
|
30
|
+
// TODO: Test cleanup
|
|
31
|
+
// const { unmount } = renderHook(() => useHookName());
|
|
32
|
+
// const cleanup = vi.fn();
|
|
33
|
+
// unmount();
|
|
34
|
+
// expect(cleanup).toHaveBeenCalled();
|
|
35
|
+
});
|
|
36
|
+
});
|