@idealyst/cli 1.2.0 → 1.2.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 (324) hide show
  1. package/dist/commands/add.js +233 -0
  2. package/dist/commands/add.js.map +1 -0
  3. package/dist/commands/index.js +13 -0
  4. package/dist/commands/index.js.map +1 -0
  5. package/dist/commands/info.js +193 -0
  6. package/dist/commands/info.js.map +1 -0
  7. package/dist/commands/init.js +155 -0
  8. package/dist/commands/init.js.map +1 -0
  9. package/dist/constants.js +119 -0
  10. package/dist/constants.js.map +1 -0
  11. package/dist/generators/core/index.js +23 -0
  12. package/dist/generators/core/index.js.map +1 -0
  13. package/dist/generators/core/mobile.js +286 -0
  14. package/dist/generators/core/mobile.js.map +1 -0
  15. package/dist/generators/core/shared.js +207 -0
  16. package/dist/generators/core/shared.js.map +1 -0
  17. package/dist/generators/core/web.js +377 -0
  18. package/dist/generators/core/web.js.map +1 -0
  19. package/dist/generators/extensions/api.js +223 -0
  20. package/dist/generators/extensions/api.js.map +1 -0
  21. package/dist/generators/extensions/devcontainer.js +510 -0
  22. package/dist/generators/extensions/devcontainer.js.map +1 -0
  23. package/dist/generators/extensions/graphql.js +202 -0
  24. package/dist/generators/extensions/graphql.js.map +1 -0
  25. package/dist/generators/extensions/index.js +17 -0
  26. package/dist/generators/extensions/index.js.map +1 -0
  27. package/dist/generators/extensions/prisma.js +249 -0
  28. package/dist/generators/extensions/prisma.js.map +1 -0
  29. package/dist/generators/extensions/trpc.js +258 -0
  30. package/dist/generators/extensions/trpc.js.map +1 -0
  31. package/dist/generators/index.js +106 -4
  32. package/dist/generators/index.js.map +1 -1
  33. package/dist/generators/reactNative.js +177 -0
  34. package/dist/generators/reactNative.js.map +1 -0
  35. package/dist/generators/workspace.js +272 -0
  36. package/dist/generators/workspace.js.map +1 -0
  37. package/dist/identifiers/android.js +75 -0
  38. package/dist/identifiers/android.js.map +1 -0
  39. package/dist/identifiers/index.js +35 -0
  40. package/dist/identifiers/index.js.map +1 -0
  41. package/dist/identifiers/ios.js +65 -0
  42. package/dist/identifiers/ios.js.map +1 -0
  43. package/dist/index.js +41 -319
  44. package/dist/index.js.map +1 -1
  45. package/dist/templates/copier.js +135 -0
  46. package/dist/templates/copier.js.map +1 -0
  47. package/dist/templates/index.js +23 -0
  48. package/dist/templates/index.js.map +1 -0
  49. package/dist/templates/merger.js +113 -0
  50. package/dist/templates/merger.js.map +1 -0
  51. package/dist/templates/processor.js +142 -0
  52. package/dist/templates/processor.js.map +1 -0
  53. package/dist/types/commands/add.d.ts +14 -0
  54. package/dist/types/commands/index.d.ts +6 -0
  55. package/dist/types/commands/info.d.ts +10 -0
  56. package/dist/types/commands/init.d.ts +19 -0
  57. package/dist/types/constants.d.ts +76 -0
  58. package/dist/types/generators/core/index.d.ts +6 -0
  59. package/dist/types/generators/core/mobile.d.ts +13 -0
  60. package/dist/types/generators/core/shared.d.ts +8 -0
  61. package/dist/types/generators/core/web.d.ts +8 -0
  62. package/dist/types/generators/extensions/api.d.ts +8 -0
  63. package/dist/types/generators/extensions/devcontainer.d.ts +15 -0
  64. package/dist/types/generators/extensions/graphql.d.ts +8 -0
  65. package/dist/types/generators/extensions/index.d.ts +8 -0
  66. package/dist/types/generators/extensions/prisma.d.ts +8 -0
  67. package/dist/types/generators/extensions/trpc.d.ts +9 -0
  68. package/dist/types/generators/index.d.ts +11 -3
  69. package/dist/types/generators/reactNative.d.ts +19 -0
  70. package/dist/types/generators/workspace.d.ts +8 -0
  71. package/dist/types/identifiers/android.d.ts +26 -0
  72. package/dist/types/identifiers/index.d.ts +26 -0
  73. package/dist/types/identifiers/ios.d.ts +21 -0
  74. package/dist/types/index.d.ts +3 -0
  75. package/dist/types/templates/copier.d.ts +24 -0
  76. package/dist/types/templates/index.d.ts +6 -0
  77. package/dist/types/templates/merger.d.ts +43 -0
  78. package/dist/types/templates/processor.d.ts +39 -0
  79. package/dist/types/types.d.ts +153 -5
  80. package/dist/types/utils/filesystem.d.ts +56 -0
  81. package/dist/types/utils/index.d.ts +7 -0
  82. package/dist/types/utils/logger.d.ts +58 -0
  83. package/dist/types/utils/shell.d.ts +35 -0
  84. package/dist/types/utils/validation.d.ts +32 -0
  85. package/dist/types/wizard/index.d.ts +20 -0
  86. package/dist/types/wizard/steps/apiExtension.d.ts +5 -0
  87. package/dist/types/wizard/steps/appDisplayName.d.ts +5 -0
  88. package/dist/types/wizard/steps/devcontainerExtension.d.ts +5 -0
  89. package/dist/types/wizard/steps/graphqlExtension.d.ts +5 -0
  90. package/dist/types/wizard/steps/index.d.ts +12 -0
  91. package/dist/types/wizard/steps/orgDomain.d.ts +5 -0
  92. package/dist/types/wizard/steps/prismaExtension.d.ts +5 -0
  93. package/dist/types/wizard/steps/projectName.d.ts +5 -0
  94. package/dist/types/wizard/steps/summary.d.ts +5 -0
  95. package/dist/types/wizard/steps/trpcExtension.d.ts +5 -0
  96. package/dist/types/wizard/validators.d.ts +17 -0
  97. package/dist/types.js +41 -0
  98. package/dist/types.js.map +1 -1
  99. package/dist/utils/filesystem.js +173 -0
  100. package/dist/utils/filesystem.js.map +1 -0
  101. package/dist/utils/index.js +24 -0
  102. package/dist/utils/index.js.map +1 -0
  103. package/dist/utils/logger.js +95 -0
  104. package/dist/utils/logger.js.map +1 -0
  105. package/dist/utils/shell.js +113 -0
  106. package/dist/utils/shell.js.map +1 -0
  107. package/dist/utils/validation.js +196 -0
  108. package/dist/utils/validation.js.map +1 -0
  109. package/dist/wizard/index.js +177 -0
  110. package/dist/wizard/index.js.map +1 -0
  111. package/dist/wizard/steps/apiExtension.js +33 -0
  112. package/dist/wizard/steps/apiExtension.js.map +1 -0
  113. package/dist/wizard/steps/appDisplayName.js +53 -0
  114. package/dist/wizard/steps/appDisplayName.js.map +1 -0
  115. package/dist/wizard/steps/devcontainerExtension.js +78 -0
  116. package/dist/wizard/steps/devcontainerExtension.js.map +1 -0
  117. package/dist/wizard/steps/graphqlExtension.js +37 -0
  118. package/dist/wizard/steps/graphqlExtension.js.map +1 -0
  119. package/dist/wizard/steps/index.js +25 -0
  120. package/dist/wizard/steps/index.js.map +1 -0
  121. package/dist/wizard/steps/orgDomain.js +40 -0
  122. package/dist/wizard/steps/orgDomain.js.map +1 -0
  123. package/dist/wizard/steps/prismaExtension.js +33 -0
  124. package/dist/wizard/steps/prismaExtension.js.map +1 -0
  125. package/dist/wizard/steps/projectName.js +40 -0
  126. package/dist/wizard/steps/projectName.js.map +1 -0
  127. package/dist/wizard/steps/summary.js +107 -0
  128. package/dist/wizard/steps/summary.js.map +1 -0
  129. package/dist/wizard/steps/trpcExtension.js +37 -0
  130. package/dist/wizard/steps/trpcExtension.js.map +1 -0
  131. package/dist/wizard/validators.js +35 -0
  132. package/dist/wizard/validators.js.map +1 -0
  133. package/package.json +6 -19
  134. package/README.md +0 -110
  135. package/dist/generators/init.js +0 -268
  136. package/dist/generators/init.js.map +0 -1
  137. package/dist/generators/utils.js +0 -682
  138. package/dist/generators/utils.js.map +0 -1
  139. package/dist/scripts/configure-react-native-monorepo.js +0 -144
  140. package/dist/scripts/configure-react-native-monorepo.js.map +0 -1
  141. package/dist/template/.devcontainer/Dockerfile +0 -26
  142. package/dist/template/.devcontainer/devcontainer.json +0 -113
  143. package/dist/template/.devcontainer/docker-compose.yml +0 -59
  144. package/dist/template/.devcontainer/figma-mcp.sh +0 -32
  145. package/dist/template/.devcontainer/gitignore.template +0 -2
  146. package/dist/template/.devcontainer/setup.sh +0 -45
  147. package/dist/template/DOCKER.md +0 -0
  148. package/dist/template/Dockerfile +0 -111
  149. package/dist/template/README.md +0 -233
  150. package/dist/template/docker/nginx/prod.conf +0 -238
  151. package/dist/template/docker/nginx.conf +0 -131
  152. package/dist/template/docker/postgres/init.sql +0 -41
  153. package/dist/template/docker/prometheus/prometheus.yml +0 -52
  154. package/dist/template/docker-compose.prod.yml +0 -146
  155. package/dist/template/docker-compose.yml +0 -143
  156. package/dist/template/dockerignore.template +0 -151
  157. package/dist/template/env.example.template +0 -36
  158. package/dist/template/gitignore.template +0 -56
  159. package/dist/template/jest.config.js +0 -20
  160. package/dist/template/mcp.json.template +0 -8
  161. package/dist/template/package.json +0 -45
  162. package/dist/template/packages/api/README.md +0 -510
  163. package/dist/template/packages/api/__tests__/api.test.ts +0 -26
  164. package/dist/template/packages/api/env.example.template +0 -6
  165. package/dist/template/packages/api/gitignore.template +0 -35
  166. package/dist/template/packages/api/jest.config.js +0 -23
  167. package/dist/template/packages/api/jest.setup.js +0 -9
  168. package/dist/template/packages/api/package.json +0 -66
  169. package/dist/template/packages/api/src/context.ts +0 -52
  170. package/dist/template/packages/api/src/controllers/TestController.ts +0 -0
  171. package/dist/template/packages/api/src/graphql/builder.ts +0 -75
  172. package/dist/template/packages/api/src/graphql/generated.ts +0 -64
  173. package/dist/template/packages/api/src/graphql/index.ts +0 -75
  174. package/dist/template/packages/api/src/graphql/types/index.ts +0 -44
  175. package/dist/template/packages/api/src/graphql/types/test.ts +0 -245
  176. package/dist/template/packages/api/src/index.ts +0 -26
  177. package/dist/template/packages/api/src/lib/database.ts +0 -23
  178. package/dist/template/packages/api/src/router/index.ts +0 -163
  179. package/dist/template/packages/api/src/routers/test.ts +0 -161
  180. package/dist/template/packages/api/src/server.ts +0 -68
  181. package/dist/template/packages/api/src/trpc.ts +0 -28
  182. package/dist/template/packages/api/tsconfig.json +0 -44
  183. package/dist/template/packages/database/README.md +0 -162
  184. package/dist/template/packages/database/gitignore.template +0 -41
  185. package/dist/template/packages/database/package.json +0 -49
  186. package/dist/template/packages/database/prisma/seed.ts +0 -64
  187. package/dist/template/packages/database/schema.prisma +0 -107
  188. package/dist/template/packages/database/src/index.ts +0 -15
  189. package/dist/template/packages/database/src/validators.ts +0 -10
  190. package/dist/template/packages/database/tsconfig.json +0 -18
  191. package/dist/template/packages/mobile/README.md +0 -86
  192. package/dist/template/packages/mobile/__tests__/App.test.tsx +0 -156
  193. package/dist/template/packages/mobile/__tests__/components.test.tsx +0 -300
  194. package/dist/template/packages/mobile/app.json +0 -5
  195. package/dist/template/packages/mobile/babel.config.js +0 -10
  196. package/dist/template/packages/mobile/gitignore.template +0 -73
  197. package/dist/template/packages/mobile/index.js +0 -6
  198. package/dist/template/packages/mobile/jest.config.js +0 -21
  199. package/dist/template/packages/mobile/jest.setup.js +0 -12
  200. package/dist/template/packages/mobile/metro.config.js +0 -36
  201. package/dist/template/packages/mobile/package.json +0 -60
  202. package/dist/template/packages/mobile/src/App.tsx +0 -8
  203. package/dist/template/packages/mobile/src/utils/trpc.ts +0 -7
  204. package/dist/template/packages/mobile/tsconfig.json +0 -28
  205. package/dist/template/packages/shared/README.md +0 -135
  206. package/dist/template/packages/shared/__tests__/shared.test.ts +0 -51
  207. package/dist/template/packages/shared/gitignore.template +0 -35
  208. package/dist/template/packages/shared/jest.config.js +0 -22
  209. package/dist/template/packages/shared/package.json +0 -68
  210. package/dist/template/packages/shared/src/components/App.tsx +0 -57
  211. package/dist/template/packages/shared/src/components/HelloWorld.tsx +0 -531
  212. package/dist/template/packages/shared/src/components/index.ts +0 -1
  213. package/dist/template/packages/shared/src/graphql/client.ts +0 -34
  214. package/dist/template/packages/shared/src/index.ts +0 -22
  215. package/dist/template/packages/shared/src/navigation/AppRouter.tsx +0 -565
  216. package/dist/template/packages/shared/src/trpc/client.ts +0 -44
  217. package/dist/template/packages/shared/tsconfig.json +0 -22
  218. package/dist/template/packages/web/README.md +0 -131
  219. package/dist/template/packages/web/__tests__/App.test.tsx +0 -342
  220. package/dist/template/packages/web/__tests__/components.test.tsx +0 -564
  221. package/dist/template/packages/web/gitignore.template +0 -35
  222. package/dist/template/packages/web/index.html +0 -13
  223. package/dist/template/packages/web/jest.config.js +0 -27
  224. package/dist/template/packages/web/jest.setup.js +0 -24
  225. package/dist/template/packages/web/package.json +0 -69
  226. package/dist/template/packages/web/src/App.tsx +0 -14
  227. package/dist/template/packages/web/src/components/TestDemo.tsx +0 -164
  228. package/dist/template/packages/web/src/main.tsx +0 -25
  229. package/dist/template/packages/web/src/utils/trpc.ts +0 -7
  230. package/dist/template/packages/web/tsconfig.json +0 -26
  231. package/dist/template/packages/web/vite.config.ts +0 -98
  232. package/dist/template/setup.sh +0 -30
  233. package/dist/template/tsconfig.json +0 -22
  234. package/dist/template/yarnrc.yml.template +0 -4
  235. package/dist/types/generators/init.d.ts +0 -5
  236. package/dist/types/generators/utils.d.ts +0 -45
  237. package/dist/types/scripts/configure-react-native-monorepo.d.ts +0 -23
  238. package/template/.devcontainer/Dockerfile +0 -26
  239. package/template/.devcontainer/devcontainer.json +0 -113
  240. package/template/.devcontainer/docker-compose.yml +0 -59
  241. package/template/.devcontainer/figma-mcp.sh +0 -32
  242. package/template/.devcontainer/setup.sh +0 -45
  243. package/template/.dockerignore +0 -151
  244. package/template/.env.example +0 -36
  245. package/template/.env.production +0 -56
  246. package/template/DOCKER.md +0 -0
  247. package/template/Dockerfile +0 -111
  248. package/template/README.md +0 -233
  249. package/template/docker/nginx/prod.conf +0 -238
  250. package/template/docker/nginx.conf +0 -131
  251. package/template/docker/postgres/init.sql +0 -41
  252. package/template/docker/prometheus/prometheus.yml +0 -52
  253. package/template/docker-compose.prod.yml +0 -146
  254. package/template/docker-compose.yml +0 -143
  255. package/template/jest.config.js +0 -20
  256. package/template/package.json +0 -45
  257. package/template/packages/api/.env.example +0 -6
  258. package/template/packages/api/README.md +0 -510
  259. package/template/packages/api/__tests__/api.test.ts +0 -26
  260. package/template/packages/api/jest.config.js +0 -23
  261. package/template/packages/api/jest.setup.js +0 -9
  262. package/template/packages/api/package.json +0 -66
  263. package/template/packages/api/src/context.ts +0 -52
  264. package/template/packages/api/src/controllers/TestController.ts +0 -0
  265. package/template/packages/api/src/graphql/builder.ts +0 -75
  266. package/template/packages/api/src/graphql/generated.ts +0 -64
  267. package/template/packages/api/src/graphql/index.ts +0 -75
  268. package/template/packages/api/src/graphql/types/index.ts +0 -44
  269. package/template/packages/api/src/graphql/types/test.ts +0 -245
  270. package/template/packages/api/src/index.ts +0 -26
  271. package/template/packages/api/src/lib/database.ts +0 -23
  272. package/template/packages/api/src/router/index.ts +0 -163
  273. package/template/packages/api/src/routers/test.ts +0 -161
  274. package/template/packages/api/src/server.ts +0 -68
  275. package/template/packages/api/src/trpc.ts +0 -28
  276. package/template/packages/api/tsconfig.json +0 -44
  277. package/template/packages/database/README.md +0 -162
  278. package/template/packages/database/package.json +0 -49
  279. package/template/packages/database/prisma/seed.ts +0 -64
  280. package/template/packages/database/schema.prisma +0 -107
  281. package/template/packages/database/src/index.ts +0 -15
  282. package/template/packages/database/src/validators.ts +0 -10
  283. package/template/packages/database/tsconfig.json +0 -18
  284. package/template/packages/mobile/README.md +0 -86
  285. package/template/packages/mobile/__tests__/App.test.tsx +0 -156
  286. package/template/packages/mobile/__tests__/components.test.tsx +0 -300
  287. package/template/packages/mobile/app.json +0 -5
  288. package/template/packages/mobile/babel.config.js +0 -26
  289. package/template/packages/mobile/index.js +0 -8
  290. package/template/packages/mobile/jest.config.js +0 -21
  291. package/template/packages/mobile/jest.setup.js +0 -12
  292. package/template/packages/mobile/metro.config.js +0 -36
  293. package/template/packages/mobile/package.json +0 -60
  294. package/template/packages/mobile/src/App.tsx +0 -8
  295. package/template/packages/mobile/src/utils/trpc.ts +0 -7
  296. package/template/packages/mobile/tsconfig.json +0 -28
  297. package/template/packages/shared/README.md +0 -135
  298. package/template/packages/shared/__tests__/shared.test.ts +0 -51
  299. package/template/packages/shared/jest.config.js +0 -22
  300. package/template/packages/shared/package.json +0 -68
  301. package/template/packages/shared/src/components/App.tsx +0 -57
  302. package/template/packages/shared/src/components/HelloWorld.tsx +0 -531
  303. package/template/packages/shared/src/components/index.ts +0 -1
  304. package/template/packages/shared/src/graphql/client.ts +0 -34
  305. package/template/packages/shared/src/index.ts +0 -25
  306. package/template/packages/shared/src/navigation/AppRouter.tsx +0 -565
  307. package/template/packages/shared/src/trpc/client.ts +0 -44
  308. package/template/packages/shared/src/unistyles.ts +0 -50
  309. package/template/packages/shared/tsconfig.json +0 -22
  310. package/template/packages/web/README.md +0 -131
  311. package/template/packages/web/__tests__/App.test.tsx +0 -342
  312. package/template/packages/web/__tests__/components.test.tsx +0 -564
  313. package/template/packages/web/index.html +0 -13
  314. package/template/packages/web/jest.config.js +0 -27
  315. package/template/packages/web/jest.setup.js +0 -24
  316. package/template/packages/web/package.json +0 -69
  317. package/template/packages/web/src/App.tsx +0 -14
  318. package/template/packages/web/src/components/TestDemo.tsx +0 -164
  319. package/template/packages/web/src/main.tsx +0 -27
  320. package/template/packages/web/src/utils/trpc.ts +0 -7
  321. package/template/packages/web/tsconfig.json +0 -26
  322. package/template/packages/web/vite.config.ts +0 -114
  323. package/template/setup.sh +0 -30
  324. package/template/tsconfig.json +0 -22
@@ -1,564 +0,0 @@
1
- /**
2
- * Example component tests for React Web
3
- * Demonstrates comprehensive testing patterns with React Testing Library
4
- */
5
-
6
- import React from 'react';
7
- import { render, screen, fireEvent, waitFor } from '@testing-library/react';
8
- import userEvent from '@testing-library/user-event';
9
-
10
- describe('React Web Component Testing Examples', () => {
11
- // Example 1: Simple Display Component
12
- const WelcomeMessage = ({
13
- name,
14
- showGreeting = true
15
- }: {
16
- name: string;
17
- showGreeting?: boolean;
18
- }) => (
19
- <div data-testid="welcome-message">
20
- {showGreeting && <h1>Welcome, {name}!</h1>}
21
- <p>Thank you for visiting our site.</p>
22
- </div>
23
- );
24
-
25
- describe('WelcomeMessage Component', () => {
26
- it('renders welcome message with name', () => {
27
- render(<WelcomeMessage name="John" />);
28
-
29
- expect(screen.getByText('Welcome, John!')).toBeInTheDocument();
30
- expect(screen.getByText('Thank you for visiting our site.')).toBeInTheDocument();
31
- });
32
-
33
- it('hides greeting when showGreeting is false', () => {
34
- render(<WelcomeMessage name="John" showGreeting={false} />);
35
-
36
- expect(screen.queryByText('Welcome, John!')).not.toBeInTheDocument();
37
- expect(screen.getByText('Thank you for visiting our site.')).toBeInTheDocument();
38
- });
39
- });
40
-
41
- // Example 2: Interactive Button Component
42
- const ActionButton = ({
43
- children,
44
- onClick,
45
- variant = 'primary',
46
- disabled = false,
47
- loading = false
48
- }: {
49
- children: React.ReactNode;
50
- onClick: () => void;
51
- variant?: 'primary' | 'secondary' | 'danger';
52
- disabled?: boolean;
53
- loading?: boolean;
54
- }) => (
55
- <button
56
- data-testid="action-button"
57
- onClick={onClick}
58
- disabled={disabled || loading}
59
- className={`btn btn-${variant} ${loading ? 'loading' : ''}`}
60
- aria-label={loading ? 'Loading...' : undefined}
61
- >
62
- {loading ? 'Loading...' : children}
63
- </button>
64
- );
65
-
66
- describe('ActionButton Component', () => {
67
- it('renders button with correct text', () => {
68
- const mockClick = jest.fn();
69
- render(<ActionButton onClick={mockClick}>Click me</ActionButton>);
70
-
71
- const button = screen.getByTestId('action-button');
72
- expect(button).toHaveTextContent('Click me');
73
- expect(button).toHaveClass('btn', 'btn-primary');
74
- });
75
-
76
- it('calls onClick when clicked', async () => {
77
- const user = userEvent.setup();
78
- const mockClick = jest.fn();
79
-
80
- render(<ActionButton onClick={mockClick}>Click me</ActionButton>);
81
-
82
- await user.click(screen.getByTestId('action-button'));
83
- expect(mockClick).toHaveBeenCalledTimes(1);
84
- });
85
-
86
- it('applies correct variant class', () => {
87
- const mockClick = jest.fn();
88
- render(<ActionButton onClick={mockClick} variant="danger">Delete</ActionButton>);
89
-
90
- expect(screen.getByTestId('action-button')).toHaveClass('btn-danger');
91
- });
92
-
93
- it('shows loading state', () => {
94
- const mockClick = jest.fn();
95
- render(<ActionButton onClick={mockClick} loading>Submit</ActionButton>);
96
-
97
- const button = screen.getByTestId('action-button');
98
- expect(button).toHaveTextContent('Loading...');
99
- expect(button).toBeDisabled();
100
- expect(button).toHaveClass('loading');
101
- expect(button).toHaveAttribute('aria-label', 'Loading...');
102
- });
103
-
104
- it('is disabled when disabled prop is true', () => {
105
- const mockClick = jest.fn();
106
- render(<ActionButton onClick={mockClick} disabled>Disabled</ActionButton>);
107
-
108
- expect(screen.getByTestId('action-button')).toBeDisabled();
109
- });
110
- });
111
-
112
- // Example 3: Form Component with Validation
113
- const ContactForm = ({
114
- onSubmit
115
- }: {
116
- onSubmit: (data: { name: string; email: string; message: string }) => void;
117
- }) => {
118
- const [formData, setFormData] = React.useState({
119
- name: '',
120
- email: '',
121
- message: ''
122
- });
123
- const [errors, setErrors] = React.useState<Record<string, string>>({});
124
-
125
- const validateForm = () => {
126
- const newErrors: Record<string, string> = {};
127
-
128
- if (!formData.name.trim()) {
129
- newErrors.name = 'Name is required';
130
- }
131
-
132
- if (!formData.email.trim()) {
133
- newErrors.email = 'Email is required';
134
- } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
135
- newErrors.email = 'Email is invalid';
136
- }
137
-
138
- if (!formData.message.trim()) {
139
- newErrors.message = 'Message is required';
140
- }
141
-
142
- setErrors(newErrors);
143
- return Object.keys(newErrors).length === 0;
144
- };
145
-
146
- const handleSubmit = (e: React.FormEvent) => {
147
- e.preventDefault();
148
- if (validateForm()) {
149
- onSubmit(formData);
150
- }
151
- };
152
-
153
- const handleInputChange = (field: string, value: string) => {
154
- setFormData(prev => ({ ...prev, [field]: value }));
155
- if (errors[field]) {
156
- setErrors(prev => ({ ...prev, [field]: '' }));
157
- }
158
- };
159
-
160
- return (
161
- <form data-testid="contact-form" onSubmit={handleSubmit}>
162
- <div>
163
- <label htmlFor="name">Name:</label>
164
- <input
165
- id="name"
166
- data-testid="name-input"
167
- type="text"
168
- value={formData.name}
169
- onChange={(e) => handleInputChange('name', e.target.value)}
170
- aria-describedby={errors.name ? 'name-error' : undefined}
171
- />
172
- {errors.name && (
173
- <div id="name-error" data-testid="name-error" role="alert">
174
- {errors.name}
175
- </div>
176
- )}
177
- </div>
178
-
179
- <div>
180
- <label htmlFor="email">Email:</label>
181
- <input
182
- id="email"
183
- data-testid="email-input"
184
- type="email"
185
- value={formData.email}
186
- onChange={(e) => handleInputChange('email', e.target.value)}
187
- aria-describedby={errors.email ? 'email-error' : undefined}
188
- />
189
- {errors.email && (
190
- <div id="email-error" data-testid="email-error" role="alert">
191
- {errors.email}
192
- </div>
193
- )}
194
- </div>
195
-
196
- <div>
197
- <label htmlFor="message">Message:</label>
198
- <textarea
199
- id="message"
200
- data-testid="message-input"
201
- value={formData.message}
202
- onChange={(e) => handleInputChange('message', e.target.value)}
203
- aria-describedby={errors.message ? 'message-error' : undefined}
204
- />
205
- {errors.message && (
206
- <div id="message-error" data-testid="message-error" role="alert">
207
- {errors.message}
208
- </div>
209
- )}
210
- </div>
211
-
212
- <button data-testid="submit-button" type="submit">
213
- Send Message
214
- </button>
215
- </form>
216
- );
217
- };
218
-
219
- describe('ContactForm Component', () => {
220
- it('renders all form fields', () => {
221
- const mockSubmit = jest.fn();
222
- render(<ContactForm onSubmit={mockSubmit} />);
223
-
224
- expect(screen.getByLabelText('Name:')).toBeInTheDocument();
225
- expect(screen.getByLabelText('Email:')).toBeInTheDocument();
226
- expect(screen.getByLabelText('Message:')).toBeInTheDocument();
227
- expect(screen.getByRole('button', { name: 'Send Message' })).toBeInTheDocument();
228
- });
229
-
230
- it('submits form with valid data', async () => {
231
- const user = userEvent.setup();
232
- const mockSubmit = jest.fn();
233
-
234
- render(<ContactForm onSubmit={mockSubmit} />);
235
-
236
- await user.type(screen.getByLabelText('Name:'), 'John Doe');
237
- await user.type(screen.getByLabelText('Email:'), 'john@example.com');
238
- await user.type(screen.getByLabelText('Message:'), 'Hello there!');
239
-
240
- await user.click(screen.getByRole('button', { name: 'Send Message' }));
241
-
242
- expect(mockSubmit).toHaveBeenCalledWith({
243
- name: 'John Doe',
244
- email: 'john@example.com',
245
- message: 'Hello there!'
246
- });
247
- });
248
-
249
- it('shows validation errors for empty fields', async () => {
250
- const user = userEvent.setup();
251
- const mockSubmit = jest.fn();
252
-
253
- render(<ContactForm onSubmit={mockSubmit} />);
254
-
255
- await user.click(screen.getByRole('button', { name: 'Send Message' }));
256
-
257
- expect(screen.getByTestId('name-error')).toHaveTextContent('Name is required');
258
- expect(screen.getByTestId('email-error')).toHaveTextContent('Email is required');
259
- expect(screen.getByTestId('message-error')).toHaveTextContent('Message is required');
260
- expect(mockSubmit).not.toHaveBeenCalled();
261
- });
262
-
263
- it('shows email validation error for invalid email', async () => {
264
- const user = userEvent.setup();
265
- const mockSubmit = jest.fn();
266
-
267
- render(<ContactForm onSubmit={mockSubmit} />);
268
-
269
- await user.type(screen.getByLabelText('Email:'), 'invalid-email');
270
- await user.click(screen.getByRole('button', { name: 'Send Message' }));
271
-
272
- expect(screen.getByTestId('email-error')).toHaveTextContent('Email is invalid');
273
- });
274
-
275
- it('clears errors when user starts typing', async () => {
276
- const user = userEvent.setup();
277
- const mockSubmit = jest.fn();
278
-
279
- render(<ContactForm onSubmit={mockSubmit} />);
280
-
281
- // Trigger validation errors
282
- await user.click(screen.getByRole('button', { name: 'Send Message' }));
283
- expect(screen.getByTestId('name-error')).toBeInTheDocument();
284
-
285
- // Start typing in name field
286
- await user.type(screen.getByLabelText('Name:'), 'J');
287
-
288
- // Error should be cleared
289
- expect(screen.queryByTestId('name-error')).not.toBeInTheDocument();
290
- });
291
- });
292
-
293
- // Example 4: Data Fetching Component
294
- const UserProfile = ({ userId }: { userId: string }) => {
295
- const [user, setUser] = React.useState<{ id: string; name: string; email: string } | null>(null);
296
- const [loading, setLoading] = React.useState(true);
297
- const [error, setError] = React.useState<string | null>(null);
298
-
299
- const fetchUser = React.useCallback(async () => {
300
- setLoading(true);
301
- setError(null);
302
-
303
- try {
304
- // Simulate API call
305
- await new Promise(resolve => setTimeout(resolve, 100));
306
-
307
- if (userId === 'invalid') {
308
- throw new Error('User not found');
309
- }
310
-
311
- setUser({
312
- id: userId,
313
- name: `User ${userId}`,
314
- email: `user${userId}@example.com`
315
- });
316
- } catch (err) {
317
- setError(err instanceof Error ? err.message : 'An error occurred');
318
- } finally {
319
- setLoading(false);
320
- }
321
- }, [userId]);
322
-
323
- React.useEffect(() => {
324
- fetchUser();
325
- }, [fetchUser]);
326
-
327
- if (loading) {
328
- return <div data-testid="loading">Loading user...</div>;
329
- }
330
-
331
- if (error) {
332
- return (
333
- <div data-testid="error">
334
- <p>Error: {error}</p>
335
- <button data-testid="retry-button" onClick={fetchUser}>
336
- Retry
337
- </button>
338
- </div>
339
- );
340
- }
341
-
342
- if (!user) {
343
- return <div data-testid="no-user">No user found</div>;
344
- }
345
-
346
- return (
347
- <div data-testid="user-profile">
348
- <h2>{user.name}</h2>
349
- <p>Email: {user.email}</p>
350
- <p>ID: {user.id}</p>
351
- </div>
352
- );
353
- };
354
-
355
- describe('UserProfile Component', () => {
356
- it('shows loading state initially', () => {
357
- render(<UserProfile userId="123" />);
358
-
359
- expect(screen.getByTestId('loading')).toBeInTheDocument();
360
- });
361
-
362
- it('shows user data after successful fetch', async () => {
363
- render(<UserProfile userId="123" />);
364
-
365
- await waitFor(() => {
366
- expect(screen.getByTestId('user-profile')).toBeInTheDocument();
367
- });
368
-
369
- expect(screen.getByText('User 123')).toBeInTheDocument();
370
- expect(screen.getByText('Email: user123@example.com')).toBeInTheDocument();
371
- expect(screen.getByText('ID: 123')).toBeInTheDocument();
372
- });
373
-
374
- it('shows error state when fetch fails', async () => {
375
- render(<UserProfile userId="invalid" />);
376
-
377
- await waitFor(() => {
378
- expect(screen.getByTestId('error')).toBeInTheDocument();
379
- });
380
-
381
- expect(screen.getByText('Error: User not found')).toBeInTheDocument();
382
- expect(screen.getByTestId('retry-button')).toBeInTheDocument();
383
- });
384
-
385
- it('retries fetch when retry button is clicked', async () => {
386
- const user = userEvent.setup();
387
- const { rerender } = render(<UserProfile userId="invalid" />);
388
-
389
- // Wait for error state
390
- await waitFor(() => {
391
- expect(screen.getByTestId('error')).toBeInTheDocument();
392
- });
393
-
394
- // Change userId to valid and click retry
395
- rerender(<UserProfile userId="456" />);
396
- await user.click(screen.getByTestId('retry-button'));
397
-
398
- // Should show loading then success
399
- expect(screen.getByTestId('loading')).toBeInTheDocument();
400
-
401
- await waitFor(() => {
402
- expect(screen.getByTestId('user-profile')).toBeInTheDocument();
403
- });
404
- });
405
- });
406
-
407
- // Example 5: Modal Component
408
- const Modal = ({
409
- isOpen,
410
- onClose,
411
- title,
412
- children
413
- }: {
414
- isOpen: boolean;
415
- onClose: () => void;
416
- title: string;
417
- children: React.ReactNode;
418
- }) => {
419
- React.useEffect(() => {
420
- const handleEscape = (e: KeyboardEvent) => {
421
- if (e.key === 'Escape') {
422
- onClose();
423
- }
424
- };
425
-
426
- if (isOpen) {
427
- document.addEventListener('keydown', handleEscape);
428
- document.body.style.overflow = 'hidden';
429
- }
430
-
431
- return () => {
432
- document.removeEventListener('keydown', handleEscape);
433
- document.body.style.overflow = '';
434
- };
435
- }, [isOpen, onClose]);
436
-
437
- if (!isOpen) return null;
438
-
439
- return (
440
- <div
441
- data-testid="modal-overlay"
442
- className="modal-overlay"
443
- onClick={onClose}
444
- >
445
- <div
446
- data-testid="modal-content"
447
- className="modal-content"
448
- onClick={(e) => e.stopPropagation()}
449
- role="dialog"
450
- aria-labelledby="modal-title"
451
- >
452
- <header className="modal-header">
453
- <h2 id="modal-title">{title}</h2>
454
- <button
455
- data-testid="close-button"
456
- onClick={onClose}
457
- aria-label="Close modal"
458
- >
459
- ×
460
- </button>
461
- </header>
462
- <div className="modal-body">
463
- {children}
464
- </div>
465
- </div>
466
- </div>
467
- );
468
- };
469
-
470
- describe('Modal Component', () => {
471
- it('does not render when isOpen is false', () => {
472
- render(
473
- <Modal isOpen={false} onClose={jest.fn()} title="Test Modal">
474
- <p>Modal content</p>
475
- </Modal>
476
- );
477
-
478
- expect(screen.queryByTestId('modal-overlay')).not.toBeInTheDocument();
479
- });
480
-
481
- it('renders when isOpen is true', () => {
482
- render(
483
- <Modal isOpen={true} onClose={jest.fn()} title="Test Modal">
484
- <p>Modal content</p>
485
- </Modal>
486
- );
487
-
488
- expect(screen.getByTestId('modal-overlay')).toBeInTheDocument();
489
- expect(screen.getByText('Test Modal')).toBeInTheDocument();
490
- expect(screen.getByText('Modal content')).toBeInTheDocument();
491
- });
492
-
493
- it('calls onClose when close button is clicked', async () => {
494
- const user = userEvent.setup();
495
- const mockClose = jest.fn();
496
-
497
- render(
498
- <Modal isOpen={true} onClose={mockClose} title="Test Modal">
499
- <p>Modal content</p>
500
- </Modal>
501
- );
502
-
503
- await user.click(screen.getByTestId('close-button'));
504
- expect(mockClose).toHaveBeenCalledTimes(1);
505
- });
506
-
507
- it('calls onClose when overlay is clicked', async () => {
508
- const user = userEvent.setup();
509
- const mockClose = jest.fn();
510
-
511
- render(
512
- <Modal isOpen={true} onClose={mockClose} title="Test Modal">
513
- <p>Modal content</p>
514
- </Modal>
515
- );
516
-
517
- await user.click(screen.getByTestId('modal-overlay'));
518
- expect(mockClose).toHaveBeenCalledTimes(1);
519
- });
520
-
521
- it('does not close when modal content is clicked', async () => {
522
- const user = userEvent.setup();
523
- const mockClose = jest.fn();
524
-
525
- render(
526
- <Modal isOpen={true} onClose={mockClose} title="Test Modal">
527
- <p>Modal content</p>
528
- </Modal>
529
- );
530
-
531
- await user.click(screen.getByTestId('modal-content'));
532
- expect(mockClose).not.toHaveBeenCalled();
533
- });
534
-
535
- it('closes when Escape key is pressed', async () => {
536
- const user = userEvent.setup();
537
- const mockClose = jest.fn();
538
-
539
- render(
540
- <Modal isOpen={true} onClose={mockClose} title="Test Modal">
541
- <p>Modal content</p>
542
- </Modal>
543
- );
544
-
545
- await user.keyboard('{Escape}');
546
- expect(mockClose).toHaveBeenCalledTimes(1);
547
- });
548
-
549
- it('has proper accessibility attributes', () => {
550
- render(
551
- <Modal isOpen={true} onClose={jest.fn()} title="Test Modal">
552
- <p>Modal content</p>
553
- </Modal>
554
- );
555
-
556
- const modalContent = screen.getByTestId('modal-content');
557
- expect(modalContent).toHaveAttribute('role', 'dialog');
558
- expect(modalContent).toHaveAttribute('aria-labelledby', 'modal-title');
559
-
560
- const closeButton = screen.getByTestId('close-button');
561
- expect(closeButton).toHaveAttribute('aria-label', 'Close modal');
562
- });
563
- });
564
- });
@@ -1,35 +0,0 @@
1
- # Dependencies
2
- node_modules/
3
-
4
- # Build outputs
5
- dist/
6
- build/
7
- *.tsbuildinfo
8
-
9
- # Environment variables
10
- .env
11
- .env.local
12
- .env.development.local
13
- .env.test.local
14
- .env.production.local
15
-
16
- # Logs
17
- npm-debug.log*
18
- yarn-debug.log*
19
- yarn-error.log*
20
-
21
- # Coverage
22
- coverage/
23
- *.lcov
24
- .nyc_output
25
-
26
- # IDE
27
- .vscode/
28
- .idea/
29
- *.swp
30
- *.swo
31
- *~
32
-
33
- # OS
34
- .DS_Store
35
- Thumbs.db
@@ -1,13 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>{{projectName}}</title>
8
- </head>
9
- <body>
10
- <div id="root"></div>
11
- <script type="module" src="/src/main.tsx"></script>
12
- </body>
13
- </html>
@@ -1,27 +0,0 @@
1
- /** @type {import('jest').Config} */
2
- module.exports = {
3
- preset: 'ts-jest',
4
- testEnvironment: 'jsdom',
5
- roots: ['<rootDir>/src', '<rootDir>/__tests__'],
6
- testMatch: [
7
- '**/__tests__/**/*.{ts,tsx,js}',
8
- '**/*.{test,spec}.{ts,tsx,js}'
9
- ],
10
- transform: {
11
- '^.+\\.tsx?$': 'ts-jest',
12
- },
13
- moduleNameMapping: {
14
- '\\.(css|less|scss|sass)$': 'identity-obj-proxy',
15
- },
16
- setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
17
- collectCoverageFrom: [
18
- 'src/**/*.{ts,tsx}',
19
- '!src/**/*.d.ts',
20
- '!src/**/index.ts',
21
- '!src/main.tsx',
22
- ],
23
- coverageDirectory: 'coverage',
24
- coverageReporters: ['text', 'lcov'],
25
- moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
26
- testTimeout: 10000,
27
- };
@@ -1,24 +0,0 @@
1
- // Global test setup for Web/React tests
2
- import '@testing-library/jest-dom';
3
-
4
- // Mock window.matchMedia for tests
5
- Object.defineProperty(window, 'matchMedia', {
6
- writable: true,
7
- value: jest.fn().mockImplementation(query => ({
8
- matches: false,
9
- media: query,
10
- onchange: null,
11
- addListener: jest.fn(), // deprecated
12
- removeListener: jest.fn(), // deprecated
13
- addEventListener: jest.fn(),
14
- removeEventListener: jest.fn(),
15
- dispatchEvent: jest.fn(),
16
- })),
17
- });
18
-
19
- // Mock ResizeObserver
20
- global.ResizeObserver = jest.fn().mockImplementation(() => ({
21
- observe: jest.fn(),
22
- unobserve: jest.fn(),
23
- disconnect: jest.fn(),
24
- }));