@idealyst/cli 1.0.33 → 1.0.35
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/dist/templates/api/README.md +207 -0
- package/dist/templates/api/__tests__/api.test.ts +26 -0
- package/dist/templates/api/env.example +12 -0
- package/dist/templates/api/jest.config.js +23 -0
- package/dist/templates/api/jest.setup.js +9 -0
- package/dist/templates/api/package.json +62 -0
- package/dist/templates/api/prisma/schema.prisma +21 -0
- package/dist/templates/api/src/context.ts +23 -0
- package/dist/templates/api/src/controllers/UserController.ts +102 -0
- package/dist/templates/api/src/index.ts +14 -0
- package/dist/templates/api/src/lib/controller.ts +90 -0
- package/dist/templates/api/src/lib/middleware.ts +170 -0
- package/dist/templates/api/src/middleware/auth.ts +75 -0
- package/dist/templates/api/src/middleware/common.ts +103 -0
- package/dist/templates/api/src/router/index.ts +130 -0
- package/dist/templates/api/src/server.ts +50 -0
- package/dist/templates/api/src/trpc.ts +28 -0
- package/dist/templates/api/tsconfig.json +44 -0
- package/dist/templates/native/.yarnrc.yml +19 -0
- package/dist/templates/native/App.tsx +23 -0
- package/dist/templates/native/README.md +86 -0
- package/dist/templates/native/__tests__/App.test.tsx +156 -0
- package/dist/templates/native/__tests__/components.test.tsx +300 -0
- package/dist/templates/native/app.json +5 -0
- package/dist/templates/native/babel.config.js +10 -0
- package/dist/templates/native/index.js +6 -0
- package/dist/templates/native/jest.config.js +21 -0
- package/dist/templates/native/jest.setup.js +12 -0
- package/dist/templates/native/metro.config.js +27 -0
- package/dist/templates/native/package.json +44 -0
- package/dist/templates/native/src/App-with-trpc.tsx +59 -0
- package/dist/templates/native/src/utils/trpc.ts +127 -0
- package/dist/templates/native/tsconfig.json +30 -0
- package/dist/templates/shared/README.md +109 -0
- package/dist/templates/shared/__tests__/shared.test.ts +39 -0
- package/dist/templates/shared/jest.config.js +22 -0
- package/dist/templates/shared/package.json +50 -0
- package/dist/templates/shared/rollup.config.js +43 -0
- package/dist/templates/shared/src/index.ts +1 -0
- package/dist/templates/shared/tsconfig.json +25 -0
- package/dist/templates/web/README.md +90 -0
- package/dist/templates/web/__tests__/App.test.tsx +342 -0
- package/dist/templates/web/__tests__/components.test.tsx +564 -0
- package/dist/templates/web/index.html +13 -0
- package/dist/templates/web/jest.config.js +27 -0
- package/dist/templates/web/jest.setup.js +24 -0
- package/dist/templates/web/package.json +66 -0
- package/dist/templates/web/src/App-with-trpc.tsx +67 -0
- package/dist/templates/web/src/App.tsx +15 -0
- package/dist/templates/web/src/main.tsx +25 -0
- package/dist/templates/web/src/utils/trpc.ts +93 -0
- package/dist/templates/web/tsconfig.json +27 -0
- package/dist/templates/web/vite.config.ts +69 -0
- package/dist/templates/workspace/.devcontainer/devcontainer.json +140 -0
- package/dist/templates/workspace/.devcontainer/docker-compose.yml +74 -0
- package/dist/templates/workspace/.dockerignore +151 -0
- package/dist/templates/workspace/.env.example +36 -0
- package/dist/templates/workspace/.env.production +56 -0
- package/dist/templates/workspace/.yarnrc.yml +26 -0
- package/dist/templates/workspace/DOCKER.md +0 -0
- package/dist/templates/workspace/Dockerfile +93 -0
- package/dist/templates/workspace/README.md +179 -0
- package/dist/templates/workspace/docker/nginx/prod.conf +238 -0
- package/dist/templates/workspace/docker/nginx.conf +131 -0
- package/dist/templates/workspace/docker/postgres/init.sql +41 -0
- package/dist/templates/workspace/docker/prometheus/prometheus.yml +52 -0
- package/dist/templates/workspace/docker-compose.prod.yml +146 -0
- package/dist/templates/workspace/docker-compose.yml +144 -0
- package/dist/templates/workspace/jest.config.js +20 -0
- package/dist/templates/workspace/package.json +35 -0
- package/dist/templates/workspace/scripts/docker/db-backup.sh +230 -0
- package/dist/templates/workspace/scripts/docker/deploy.sh +212 -0
- package/dist/templates/workspace/scripts/docker-build.sh +151 -0
- package/dist/templates/workspace/scripts/test-runner.js +120 -0
- package/dist/templates/workspace/setup.sh +205 -0
- package/package.json +3 -2
- package/templates/workspace/.devcontainer/Dockerfile +22 -0
- package/templates/workspace/.devcontainer/devcontainer.json +0 -140
- package/templates/workspace/.devcontainer/docker-compose.yml +13 -26
- package/templates/workspace/.devcontainer/setup.sh +64 -0
- package/templates/workspace/Dockerfile +24 -6
- /package/{templates → dist/templates}/workspace/.devcontainer/post-create.sh +0 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
5
|
+
import App from '../src/App';
|
|
6
|
+
|
|
7
|
+
// Mock the NavigatorProvider to avoid complex setup
|
|
8
|
+
jest.mock('@idealyst/navigation', () => ({
|
|
9
|
+
NavigatorProvider: ({ children }: { children?: React.ReactNode }) => (
|
|
10
|
+
<div data-testid="navigator-provider">{children || 'Navigator Content'}</div>
|
|
11
|
+
),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
jest.mock('@idealyst/navigation/examples', () => ({
|
|
15
|
+
ExampleStackRouter: {},
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
describe('App Component', () => {
|
|
19
|
+
it('renders without crashing', () => {
|
|
20
|
+
render(
|
|
21
|
+
<MemoryRouter>
|
|
22
|
+
<App />
|
|
23
|
+
</MemoryRouter>
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
expect(screen.getByTestId('navigator-provider')).toBeInTheDocument();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('contains the App class', () => {
|
|
30
|
+
const { container } = render(
|
|
31
|
+
<MemoryRouter>
|
|
32
|
+
<App />
|
|
33
|
+
</MemoryRouter>
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
expect(container.querySelector('.App')).toBeInTheDocument();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders NavigatorProvider with BrowserRouter', () => {
|
|
40
|
+
render(
|
|
41
|
+
<MemoryRouter>
|
|
42
|
+
<App />
|
|
43
|
+
</MemoryRouter>
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const navigatorProvider = screen.getByTestId('navigator-provider');
|
|
47
|
+
expect(navigatorProvider).toBeInTheDocument();
|
|
48
|
+
expect(navigatorProvider).toHaveTextContent('Navigator Content');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('Sample React Component Tests', () => {
|
|
53
|
+
// Example functional component for testing
|
|
54
|
+
const Button = ({
|
|
55
|
+
children,
|
|
56
|
+
onClick,
|
|
57
|
+
disabled = false,
|
|
58
|
+
variant = 'primary'
|
|
59
|
+
}: {
|
|
60
|
+
children: React.ReactNode;
|
|
61
|
+
onClick: () => void;
|
|
62
|
+
disabled?: boolean;
|
|
63
|
+
variant?: 'primary' | 'secondary';
|
|
64
|
+
}) => (
|
|
65
|
+
<button
|
|
66
|
+
data-testid="custom-button"
|
|
67
|
+
onClick={onClick}
|
|
68
|
+
disabled={disabled}
|
|
69
|
+
className={`btn btn-${variant}`}
|
|
70
|
+
>
|
|
71
|
+
{children}
|
|
72
|
+
</button>
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
it('renders button with correct text', () => {
|
|
76
|
+
const mockClick = jest.fn();
|
|
77
|
+
render(<Button onClick={mockClick}>Click me</Button>);
|
|
78
|
+
|
|
79
|
+
const button = screen.getByTestId('custom-button');
|
|
80
|
+
expect(button).toBeInTheDocument();
|
|
81
|
+
expect(button).toHaveTextContent('Click me');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('calls onClick when clicked', async () => {
|
|
85
|
+
const user = userEvent.setup();
|
|
86
|
+
const mockClick = jest.fn();
|
|
87
|
+
|
|
88
|
+
render(<Button onClick={mockClick}>Click me</Button>);
|
|
89
|
+
|
|
90
|
+
const button = screen.getByTestId('custom-button');
|
|
91
|
+
await user.click(button);
|
|
92
|
+
|
|
93
|
+
expect(mockClick).toHaveBeenCalledTimes(1);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('is disabled when disabled prop is true', () => {
|
|
97
|
+
const mockClick = jest.fn();
|
|
98
|
+
render(
|
|
99
|
+
<Button onClick={mockClick} disabled>
|
|
100
|
+
Disabled Button
|
|
101
|
+
</Button>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const button = screen.getByTestId('custom-button');
|
|
105
|
+
expect(button).toBeDisabled();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('applies correct CSS classes based on variant', () => {
|
|
109
|
+
const mockClick = jest.fn();
|
|
110
|
+
const { rerender } = render(
|
|
111
|
+
<Button onClick={mockClick} variant="primary">
|
|
112
|
+
Primary Button
|
|
113
|
+
</Button>
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
let button = screen.getByTestId('custom-button');
|
|
117
|
+
expect(button).toHaveClass('btn', 'btn-primary');
|
|
118
|
+
|
|
119
|
+
rerender(
|
|
120
|
+
<Button onClick={mockClick} variant="secondary">
|
|
121
|
+
Secondary Button
|
|
122
|
+
</Button>
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
button = screen.getByTestId('custom-button');
|
|
126
|
+
expect(button).toHaveClass('btn', 'btn-secondary');
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
describe('Component with State', () => {
|
|
131
|
+
const Counter = ({ initialCount = 0 }: { initialCount?: number }) => {
|
|
132
|
+
const [count, setCount] = React.useState(initialCount);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div data-testid="counter">
|
|
136
|
+
<span data-testid="count-display">Count: {count}</span>
|
|
137
|
+
<button
|
|
138
|
+
data-testid="increment-button"
|
|
139
|
+
onClick={() => setCount(c => c + 1)}
|
|
140
|
+
>
|
|
141
|
+
Increment
|
|
142
|
+
</button>
|
|
143
|
+
<button
|
|
144
|
+
data-testid="decrement-button"
|
|
145
|
+
onClick={() => setCount(c => c - 1)}
|
|
146
|
+
>
|
|
147
|
+
Decrement
|
|
148
|
+
</button>
|
|
149
|
+
<button
|
|
150
|
+
data-testid="reset-button"
|
|
151
|
+
onClick={() => setCount(initialCount)}
|
|
152
|
+
>
|
|
153
|
+
Reset
|
|
154
|
+
</button>
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
it('renders with initial count', () => {
|
|
160
|
+
render(<Counter initialCount={5} />);
|
|
161
|
+
|
|
162
|
+
expect(screen.getByTestId('count-display')).toHaveTextContent('Count: 5');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('increments count when increment button is clicked', async () => {
|
|
166
|
+
const user = userEvent.setup();
|
|
167
|
+
render(<Counter />);
|
|
168
|
+
|
|
169
|
+
const incrementButton = screen.getByTestId('increment-button');
|
|
170
|
+
const countDisplay = screen.getByTestId('count-display');
|
|
171
|
+
|
|
172
|
+
expect(countDisplay).toHaveTextContent('Count: 0');
|
|
173
|
+
|
|
174
|
+
await user.click(incrementButton);
|
|
175
|
+
expect(countDisplay).toHaveTextContent('Count: 1');
|
|
176
|
+
|
|
177
|
+
await user.click(incrementButton);
|
|
178
|
+
expect(countDisplay).toHaveTextContent('Count: 2');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('decrements count when decrement button is clicked', async () => {
|
|
182
|
+
const user = userEvent.setup();
|
|
183
|
+
render(<Counter initialCount={5} />);
|
|
184
|
+
|
|
185
|
+
const decrementButton = screen.getByTestId('decrement-button');
|
|
186
|
+
const countDisplay = screen.getByTestId('count-display');
|
|
187
|
+
|
|
188
|
+
expect(countDisplay).toHaveTextContent('Count: 5');
|
|
189
|
+
|
|
190
|
+
await user.click(decrementButton);
|
|
191
|
+
expect(countDisplay).toHaveTextContent('Count: 4');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('resets count to initial value', async () => {
|
|
195
|
+
const user = userEvent.setup();
|
|
196
|
+
render(<Counter initialCount={10} />);
|
|
197
|
+
|
|
198
|
+
const incrementButton = screen.getByTestId('increment-button');
|
|
199
|
+
const resetButton = screen.getByTestId('reset-button');
|
|
200
|
+
const countDisplay = screen.getByTestId('count-display');
|
|
201
|
+
|
|
202
|
+
// Increment a few times
|
|
203
|
+
await user.click(incrementButton);
|
|
204
|
+
await user.click(incrementButton);
|
|
205
|
+
expect(countDisplay).toHaveTextContent('Count: 12');
|
|
206
|
+
|
|
207
|
+
// Reset
|
|
208
|
+
await user.click(resetButton);
|
|
209
|
+
expect(countDisplay).toHaveTextContent('Count: 10');
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe('Async Component Testing', () => {
|
|
214
|
+
const AsyncDataComponent = () => {
|
|
215
|
+
const [data, setData] = React.useState<string | null>(null);
|
|
216
|
+
const [loading, setLoading] = React.useState(false);
|
|
217
|
+
const [error, setError] = React.useState<string | null>(null);
|
|
218
|
+
|
|
219
|
+
const fetchData = async () => {
|
|
220
|
+
setLoading(true);
|
|
221
|
+
setError(null);
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
// Simulate API call
|
|
225
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
226
|
+
setData('Fetched data successfully');
|
|
227
|
+
} catch (err) {
|
|
228
|
+
setError('Failed to fetch data');
|
|
229
|
+
} finally {
|
|
230
|
+
setLoading(false);
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<div data-testid="async-component">
|
|
236
|
+
<button data-testid="fetch-button" onClick={fetchData}>
|
|
237
|
+
Fetch Data
|
|
238
|
+
</button>
|
|
239
|
+
|
|
240
|
+
{loading && <div data-testid="loading">Loading...</div>}
|
|
241
|
+
{error && <div data-testid="error">{error}</div>}
|
|
242
|
+
{data && <div data-testid="data">{data}</div>}
|
|
243
|
+
</div>
|
|
244
|
+
);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
it('fetches and displays data', async () => {
|
|
248
|
+
const user = userEvent.setup();
|
|
249
|
+
render(<AsyncDataComponent />);
|
|
250
|
+
|
|
251
|
+
const fetchButton = screen.getByTestId('fetch-button');
|
|
252
|
+
|
|
253
|
+
// Initially no data, loading, or error
|
|
254
|
+
expect(screen.queryByTestId('data')).not.toBeInTheDocument();
|
|
255
|
+
expect(screen.queryByTestId('loading')).not.toBeInTheDocument();
|
|
256
|
+
expect(screen.queryByTestId('error')).not.toBeInTheDocument();
|
|
257
|
+
|
|
258
|
+
// Click fetch button
|
|
259
|
+
await user.click(fetchButton);
|
|
260
|
+
|
|
261
|
+
// Loading should appear
|
|
262
|
+
expect(screen.getByTestId('loading')).toBeInTheDocument();
|
|
263
|
+
|
|
264
|
+
// Wait for data to load
|
|
265
|
+
await waitFor(() => {
|
|
266
|
+
expect(screen.getByTestId('data')).toBeInTheDocument();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
expect(screen.getByTestId('data')).toHaveTextContent('Fetched data successfully');
|
|
270
|
+
expect(screen.queryByTestId('loading')).not.toBeInTheDocument();
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe('Form Component Testing', () => {
|
|
275
|
+
const ContactForm = ({ onSubmit }: { onSubmit: (data: { name: string; email: string }) => void }) => {
|
|
276
|
+
const [name, setName] = React.useState('');
|
|
277
|
+
const [email, setEmail] = React.useState('');
|
|
278
|
+
|
|
279
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
280
|
+
e.preventDefault();
|
|
281
|
+
onSubmit({ name, email });
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<form data-testid="contact-form" onSubmit={handleSubmit}>
|
|
286
|
+
<input
|
|
287
|
+
data-testid="name-input"
|
|
288
|
+
type="text"
|
|
289
|
+
placeholder="Name"
|
|
290
|
+
value={name}
|
|
291
|
+
onChange={(e) => setName(e.target.value)}
|
|
292
|
+
/>
|
|
293
|
+
<input
|
|
294
|
+
data-testid="email-input"
|
|
295
|
+
type="email"
|
|
296
|
+
placeholder="Email"
|
|
297
|
+
value={email}
|
|
298
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
299
|
+
/>
|
|
300
|
+
<button data-testid="submit-button" type="submit">
|
|
301
|
+
Submit
|
|
302
|
+
</button>
|
|
303
|
+
</form>
|
|
304
|
+
);
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
it('submits form with correct data', async () => {
|
|
308
|
+
const user = userEvent.setup();
|
|
309
|
+
const mockSubmit = jest.fn();
|
|
310
|
+
|
|
311
|
+
render(<ContactForm onSubmit={mockSubmit} />);
|
|
312
|
+
|
|
313
|
+
const nameInput = screen.getByTestId('name-input');
|
|
314
|
+
const emailInput = screen.getByTestId('email-input');
|
|
315
|
+
const submitButton = screen.getByTestId('submit-button');
|
|
316
|
+
|
|
317
|
+
// Fill out form
|
|
318
|
+
await user.type(nameInput, 'John Doe');
|
|
319
|
+
await user.type(emailInput, 'john@example.com');
|
|
320
|
+
|
|
321
|
+
// Submit form
|
|
322
|
+
await user.click(submitButton);
|
|
323
|
+
|
|
324
|
+
expect(mockSubmit).toHaveBeenCalledWith({
|
|
325
|
+
name: 'John Doe',
|
|
326
|
+
email: 'john@example.com'
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('updates input values as user types', async () => {
|
|
331
|
+
const user = userEvent.setup();
|
|
332
|
+
const mockSubmit = jest.fn();
|
|
333
|
+
|
|
334
|
+
render(<ContactForm onSubmit={mockSubmit} />);
|
|
335
|
+
|
|
336
|
+
const nameInput = screen.getByTestId('name-input') as HTMLInputElement;
|
|
337
|
+
|
|
338
|
+
await user.type(nameInput, 'Test Name');
|
|
339
|
+
|
|
340
|
+
expect(nameInput.value).toBe('Test Name');
|
|
341
|
+
});
|
|
342
|
+
});
|