@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.
@@ -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
+ });