create-fullstack-scaffold 0.1.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.
Files changed (530) hide show
  1. package/README.md +154 -0
  2. package/dist/cli/index.d.ts +2 -0
  3. package/dist/cli/index.js +439 -0
  4. package/dist/cli/index.js.map +1 -0
  5. package/package.json +171 -0
  6. package/template/.agents/skills/writing-hookify-rules/SKILL.md +408 -0
  7. package/template/.ai-context.md +368 -0
  8. package/template/.claude/hookify.block-dangerous-commands.local.md +41 -0
  9. package/template/.claude/hookify.check-api-types.local.md +111 -0
  10. package/template/.claude/hookify.check-duplicate-types.local.md +287 -0
  11. package/template/.claude/hookify.check-test-assertions.local.md +93 -0
  12. package/template/.claude/hookify.remind-add-tests.local.md +209 -0
  13. package/template/.claude/hookify.remind-db-migration.local.md +58 -0
  14. package/template/.claude/hookify.remind-run-tests.local.md +52 -0
  15. package/template/.claude/hookify.warn-any-type.local.md +203 -0
  16. package/template/.claude/hookify.warn-console-log.local.md +73 -0
  17. package/template/.claude/rules/00-project-config.md +147 -0
  18. package/template/.claude/rules/01-file-types.md +127 -0
  19. package/template/.claude/rules/02-git-workflow.md +167 -0
  20. package/template/.claude/rules/10-api-type-inference.md +555 -0
  21. package/template/.claude/rules/20-server-api.md +349 -0
  22. package/template/.claude/rules/21-server-entrypoint.md +257 -0
  23. package/template/.claude/rules/30-client-components.md +314 -0
  24. package/template/.claude/rules/31-client-services.md +768 -0
  25. package/template/.claude/rules/32-client-state-zustand.md +324 -0
  26. package/template/.claude/rules/33-client-app-entry.md +276 -0
  27. package/template/.claude/rules/34-client-admin-module.md +445 -0
  28. package/template/.claude/rules/35-cli-module.md +259 -0
  29. package/template/.claude/rules/40-admin-module.md +495 -0
  30. package/template/.claude/rules/40-shared-types.md +450 -0
  31. package/template/.claude/rules/50-websocket.md +245 -0
  32. package/template/.claude/rules/51-sse.md +372 -0
  33. package/template/.claude/rules/52-cloudflare-realtime.md +384 -0
  34. package/template/.claude/rules/60-testing-standards.md +245 -0
  35. package/template/.claude/rules/61-hono-testing.md +200 -0
  36. package/template/.claude/scripts/README.md +187 -0
  37. package/template/.claude/scripts/check-duplicate-types.ts +188 -0
  38. package/template/.claude/scripts/post-edit-check-incremental.sh +61 -0
  39. package/template/.claude/scripts/post-edit-check.sh +22 -0
  40. package/template/.claude/scripts/pre-tool-use-check.sh +86 -0
  41. package/template/.claude/scripts/test-pre-tool-use.sh +73 -0
  42. package/template/.claude/scripts/validate-git-changes.sh +38 -0
  43. package/template/.claude/settings.json +32 -0
  44. package/template/.claude/skills/writing-hookify-rules/SKILL.md +408 -0
  45. package/template/.dev-context-compact.md +54 -0
  46. package/template/.dev-context.md +621 -0
  47. package/template/.dev-guide.md +243 -0
  48. package/template/.env.example +60 -0
  49. package/template/.eslintrc.routes.json +39 -0
  50. package/template/.husky/_/husky.sh +27 -0
  51. package/template/.husky/commit-msg +4 -0
  52. package/template/.husky/post-commit +4 -0
  53. package/template/.husky/pre-commit +19 -0
  54. package/template/.husky/pre-push +12 -0
  55. package/template/.prettierignore +22 -0
  56. package/template/.prettierrc +8 -0
  57. package/template/.sessions/3/2026-03-26T05-41-09-154Z_03bb597f-3898-4a2e-8c08-5746bbe8d95e.jsonl +18 -0
  58. package/template/.sessions/3/2026-03-26T05-48-30-052Z_12de4774-9cb3-494c-9852-bd1e6b0003c3.jsonl +6 -0
  59. package/template/.vscode/extensions.json +8 -0
  60. package/template/.vscode/settings.json +4 -0
  61. package/template/.workspaces/3/hello.py +1 -0
  62. package/template/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/803c2e37182b913f8857edf6d8776515cef4af6d48fcb25913e457f4b14ffa97.sqlite +0 -0
  63. package/template/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/803c2e37182b913f8857edf6d8776515cef4af6d48fcb25913e457f4b14ffa97.sqlite-shm +0 -0
  64. package/template/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/803c2e37182b913f8857edf6d8776515cef4af6d48fcb25913e457f4b14ffa97.sqlite-wal +0 -0
  65. package/template/CLAUDE.md +296 -0
  66. package/template/DESIGN.md +260 -0
  67. package/template/DEV_CONTEXT_GUIDE.md +366 -0
  68. package/template/DEV_TOOLS_README.md +396 -0
  69. package/template/FRAMEWORK_HISTORY.md +34 -0
  70. package/template/PROJECT_SUMMARY.md +325 -0
  71. package/template/QUICKSTART.md +305 -0
  72. package/template/README.md +154 -0
  73. package/template/STRUCTURE.txt +81 -0
  74. package/template/admin.html +13 -0
  75. package/template/auth-inject.html +33 -0
  76. package/template/commitlint.config.js +12 -0
  77. package/template/docs/PERMISSION_ARCHITECTURE.md +279 -0
  78. package/template/docs/PERMISSION_EXAMPLES.md +230 -0
  79. package/template/docs/PERMISSION_SYSTEM.md +301 -0
  80. package/template/docs/SMART_TEST.md +171 -0
  81. package/template/docs/runtime-abstract.md +253 -0
  82. package/template/drizzle/0000_rainy_boomer.sql +101 -0
  83. package/template/drizzle/0001_add_todo_attachments.sql +12 -0
  84. package/template/drizzle/0002_chilly_magneto.sql +89 -0
  85. package/template/drizzle/meta/0000_snapshot.json +652 -0
  86. package/template/drizzle/meta/0001_snapshot.json +735 -0
  87. package/template/drizzle/meta/0002_snapshot.json +1198 -0
  88. package/template/drizzle/meta/_journal.json +27 -0
  89. package/template/drizzle.config.ts +13 -0
  90. package/template/eslint-rules/README-no-middleware-in-routes.md +154 -0
  91. package/template/eslint-rules/__tests__/enforce-valid-method.test.ts +251 -0
  92. package/template/eslint-rules/__tests__/layer-boundary.test.ts +102 -0
  93. package/template/eslint-rules/__tests__/module-boundary.test.ts +77 -0
  94. package/template/eslint-rules/__tests__/no-direct-zod-import-in-file-routes.test.ts +45 -0
  95. package/template/eslint-rules/__tests__/no-new-old-service-naming.test.ts +76 -0
  96. package/template/eslint-rules/e2e-test-location.js +153 -0
  97. package/template/eslint-rules/enforce-valid-method.js +217 -0
  98. package/template/eslint-rules/flat-routes-services.js +69 -0
  99. package/template/eslint-rules/framework-protect.js +186 -0
  100. package/template/eslint-rules/layer-boundary.js +164 -0
  101. package/template/eslint-rules/limit-type-complexity.js +130 -0
  102. package/template/eslint-rules/middleware-location.js +162 -0
  103. package/template/eslint-rules/module-boundary.js +125 -0
  104. package/template/eslint-rules/no-ambiguous-file-paths.js +64 -0
  105. package/template/eslint-rules/no-any-on-apiclient.js +173 -0
  106. package/template/eslint-rules/no-boolean-success.js +43 -0
  107. package/template/eslint-rules/no-deep-relative-imports.js +69 -0
  108. package/template/eslint-rules/no-direct-fetch.js +122 -0
  109. package/template/eslint-rules/no-direct-ws-sse.js +57 -0
  110. package/template/eslint-rules/no-direct-zod-import-in-file-routes.js +54 -0
  111. package/template/eslint-rules/no-disable-direct-fetch.js +57 -0
  112. package/template/eslint-rules/no-disable-type-safe-client.js +58 -0
  113. package/template/eslint-rules/no-inline-schema.js +220 -0
  114. package/template/eslint-rules/no-middleware-in-routes.js +81 -0
  115. package/template/eslint-rules/no-new-old-service-naming.js +123 -0
  116. package/template/eslint-rules/no-type-assertion-in-rpc.js +99 -0
  117. package/template/eslint-rules/no-type-assertion-on-shared-types.js +109 -0
  118. package/template/eslint-rules/no-util-functions-in-service.js +128 -0
  119. package/template/eslint-rules/prefer-shared-types.js +352 -0
  120. package/template/eslint-rules/protect-ws-sse-interface.js +66 -0
  121. package/template/eslint-rules/require-antd-generic-types.js +73 -0
  122. package/template/eslint-rules/require-file-openapi-props.js +114 -0
  123. package/template/eslint-rules/require-hono-chain-syntax.js +129 -0
  124. package/template/eslint-rules/require-nullable-for-optional.js +99 -0
  125. package/template/eslint-rules/require-response-helpers.js +140 -0
  126. package/template/eslint-rules/require-type-safe-test-client.js +106 -0
  127. package/template/eslint-rules/route-location.js +72 -0
  128. package/template/eslint.config.js +231 -0
  129. package/template/index.html +17 -0
  130. package/template/lint-scripts/DIRECTORY_RULES.md +335 -0
  131. package/template/lint-scripts/README.md +299 -0
  132. package/template/lint-scripts/TESTING.md +273 -0
  133. package/template/lint-scripts/ai-context-generator.ts +159 -0
  134. package/template/lint-scripts/check-framework-modify.sh +8 -0
  135. package/template/lint-scripts/check-menu-permissions.js +242 -0
  136. package/template/lint-scripts/check-refs.ts +165 -0
  137. package/template/lint-scripts/check-route-auth-complete.js +260 -0
  138. package/template/lint-scripts/check-route-auth.js +212 -0
  139. package/template/lint-scripts/check-route-permissions.js +210 -0
  140. package/template/lint-scripts/config/project.config.ts +358 -0
  141. package/template/lint-scripts/dev-context-analyzer.ts +707 -0
  142. package/template/lint-scripts/dev-guide-generator.ts +830 -0
  143. package/template/lint-scripts/dev-server.ts +58 -0
  144. package/template/lint-scripts/eslint-route-auth.js +158 -0
  145. package/template/lint-scripts/eslint-routes-permission.js +147 -0
  146. package/template/lint-scripts/fix-route-auth.js +245 -0
  147. package/template/lint-scripts/framework/check-modify.ts +152 -0
  148. package/template/lint-scripts/framework/hash-utils.ts +180 -0
  149. package/template/lint-scripts/framework/init-baseline.ts +84 -0
  150. package/template/lint-scripts/framework/update-baseline.ts +148 -0
  151. package/template/lint-scripts/post-commit-track.ts +66 -0
  152. package/template/lint-scripts/quick-test.sh +27 -0
  153. package/template/lint-scripts/smart-test.ts +268 -0
  154. package/template/lint-scripts/test-history.ts +360 -0
  155. package/template/lint-scripts/test-tracker.ts +350 -0
  156. package/template/lint-scripts/validate-all.ts +250 -0
  157. package/template/lint-scripts/validators/api-coverage.validator.ts +355 -0
  158. package/template/lint-scripts/validators/client-rpc.validator.ts +190 -0
  159. package/template/lint-scripts/validators/client-tests.validator.ts +155 -0
  160. package/template/lint-scripts/validators/console-log.validator.ts +122 -0
  161. package/template/lint-scripts/validators/directory-structure.validator.ts +181 -0
  162. package/template/lint-scripts/validators/imports.validator.ts +180 -0
  163. package/template/lint-scripts/validators/index.ts +271 -0
  164. package/template/lint-scripts/validators/md-refs.validator.ts +165 -0
  165. package/template/lint-scripts/validators/module-tests.validator.ts +112 -0
  166. package/template/lint-scripts/validators/sensitive.validator.ts +185 -0
  167. package/template/lint-scripts/validators/server-rpc.validator.ts +141 -0
  168. package/template/lint-scripts/validators/test-quality.validator.ts +257 -0
  169. package/template/lint-scripts/validators/todos.validator.ts +126 -0
  170. package/template/lint-scripts/watch-validator.ts +131 -0
  171. package/template/modules.config.ts +71 -0
  172. package/template/package-lock.json +14554 -0
  173. package/template/package.json +158 -0
  174. package/template/patches/@hono+zod-openapi+1.2.2.patch +67 -0
  175. package/template/patches/@hono+zod-validator+0.7.6.patch +13 -0
  176. package/template/patches/hono+4.12.16.patch +246 -0
  177. package/template/patches/typescript+5.8.3.patch +26 -0
  178. package/template/playwright.config.ts +60 -0
  179. package/template/pnpm-lock.yaml +7137 -0
  180. package/template/postcss.config.js +6 -0
  181. package/template/scripts/create-template.ts +1724 -0
  182. package/template/src/admin/App.tsx +59 -0
  183. package/template/src/admin/components/AccountSwitcher.tsx +104 -0
  184. package/template/src/admin/components/CaptchaModal.tsx +150 -0
  185. package/template/src/admin/components/NotificationDrawer.tsx +213 -0
  186. package/template/src/admin/components/PageHeader.tsx +45 -0
  187. package/template/src/admin/components/PermissionConfigEditor.tsx +237 -0
  188. package/template/src/admin/components/PermissionGuard.tsx +108 -0
  189. package/template/src/admin/components/PermissionTree.tsx +193 -0
  190. package/template/src/admin/components/ProtectedRoute.tsx +33 -0
  191. package/template/src/admin/components/StatsCard.tsx +44 -0
  192. package/template/src/admin/components/UserFormModal.tsx +67 -0
  193. package/template/src/admin/components/UserTable.tsx +72 -0
  194. package/template/src/admin/components/__tests__/PageHeader.test.tsx +48 -0
  195. package/template/src/admin/components/__tests__/PermissionConfigEditor.test.tsx +147 -0
  196. package/template/src/admin/components/__tests__/PermissionGuard.test.tsx +221 -0
  197. package/template/src/admin/components/__tests__/PermissionTree.test.tsx +187 -0
  198. package/template/src/admin/components/__tests__/ProtectedRoute.test.tsx +129 -0
  199. package/template/src/admin/components/__tests__/StatsCard.test.tsx +44 -0
  200. package/template/src/admin/components/__tests__/UserFormModal.test.tsx +125 -0
  201. package/template/src/admin/components/__tests__/UserTable.test.tsx +89 -0
  202. package/template/src/admin/components/index.ts +7 -0
  203. package/template/src/admin/hooks/__tests__/useAuditLogs.test.ts +109 -0
  204. package/template/src/admin/hooks/__tests__/usePermissions.test.tsx +327 -0
  205. package/template/src/admin/hooks/__tests__/usePermissionsBranches.test.ts +198 -0
  206. package/template/src/admin/hooks/__tests__/useRoles.test.ts +261 -0
  207. package/template/src/admin/hooks/useAdminNotifications.ts +156 -0
  208. package/template/src/admin/hooks/useAuditLogs.ts +44 -0
  209. package/template/src/admin/hooks/useConfig.ts +168 -0
  210. package/template/src/admin/hooks/usePermissions.ts +156 -0
  211. package/template/src/admin/hooks/useRoles.ts +120 -0
  212. package/template/src/admin/layouts/Header.tsx +93 -0
  213. package/template/src/admin/layouts/Layout.tsx +23 -0
  214. package/template/src/admin/layouts/Sidebar.tsx +120 -0
  215. package/template/src/admin/main.tsx +18 -0
  216. package/template/src/admin/pages/ContentPage.tsx +252 -0
  217. package/template/src/admin/pages/DashboardPage.tsx +141 -0
  218. package/template/src/admin/pages/DisputesPage.tsx +261 -0
  219. package/template/src/admin/pages/LoginPage.tsx +121 -0
  220. package/template/src/admin/pages/MediaTestPage.tsx +524 -0
  221. package/template/src/admin/pages/OrdersPage.tsx +302 -0
  222. package/template/src/admin/pages/PermissionsPage.tsx +95 -0
  223. package/template/src/admin/pages/RegisterPage.tsx +103 -0
  224. package/template/src/admin/pages/RolesPage.tsx +330 -0
  225. package/template/src/admin/pages/SettingsPage.tsx +73 -0
  226. package/template/src/admin/pages/SystemLogsPage.tsx +218 -0
  227. package/template/src/admin/pages/TestCaptchaPage.tsx +120 -0
  228. package/template/src/admin/pages/TicketsPage.tsx +292 -0
  229. package/template/src/admin/pages/UsersPage.tsx +282 -0
  230. package/template/src/admin/pages/__tests__/ContentPage.test.tsx +213 -0
  231. package/template/src/admin/pages/__tests__/DashboardPage.test.tsx +153 -0
  232. package/template/src/admin/pages/__tests__/DisputesPage.test.tsx +196 -0
  233. package/template/src/admin/pages/__tests__/LoginPage.test.tsx +112 -0
  234. package/template/src/admin/pages/__tests__/OrdersPage.test.tsx +217 -0
  235. package/template/src/admin/pages/__tests__/PermissionsPage.test.tsx +119 -0
  236. package/template/src/admin/pages/__tests__/RegisterPage.test.tsx +179 -0
  237. package/template/src/admin/pages/__tests__/RolesPage.test.tsx +256 -0
  238. package/template/src/admin/pages/__tests__/SettingsPage.test.tsx +66 -0
  239. package/template/src/admin/pages/__tests__/SystemLogsPage.test.tsx +125 -0
  240. package/template/src/admin/pages/__tests__/TicketsPage.test.tsx +202 -0
  241. package/template/src/admin/pages/__tests__/UsersPage.test.tsx +229 -0
  242. package/template/src/admin/services/README.md +263 -0
  243. package/template/src/admin/services/TESTING.md +323 -0
  244. package/template/src/admin/services/__tests__/skipLoading.test.ts +146 -0
  245. package/template/src/admin/services/apiClient.ts +66 -0
  246. package/template/src/admin/services/requestInterceptor.ts +127 -0
  247. package/template/src/admin/services/types.ts +5 -0
  248. package/template/src/admin/stores/__tests__/adminStore.test.ts +184 -0
  249. package/template/src/admin/stores/__tests__/adminStoreBranches.test.ts +148 -0
  250. package/template/src/admin/stores/__tests__/captchaStore.test.ts +75 -0
  251. package/template/src/admin/stores/__tests__/captchaStoreBranches.test.ts +37 -0
  252. package/template/src/admin/stores/__tests__/loadingStore.test.ts +68 -0
  253. package/template/src/admin/stores/adminStore.ts +88 -0
  254. package/template/src/admin/stores/captchaStore.ts +47 -0
  255. package/template/src/admin/stores/loadingStore.ts +27 -0
  256. package/template/src/cli/index.ts +22 -0
  257. package/template/src/cli/modules/config/index.ts +132 -0
  258. package/template/src/cli/modules/index.ts +12 -0
  259. package/template/src/cli/modules/notification/index.ts +86 -0
  260. package/template/src/cli/modules/todo/index.ts +60 -0
  261. package/template/src/cli/rpc/client.ts +23 -0
  262. package/template/src/cli/rpc/index.ts +1 -0
  263. package/template/src/cli/utils/api.ts +17 -0
  264. package/template/src/cli/utils/auto-command.ts +229 -0
  265. package/template/src/cli/utils/index.ts +8 -0
  266. package/template/src/cli/utils/logger.ts +79 -0
  267. package/template/src/client/App.tsx +24 -0
  268. package/template/src/client/Layout.tsx +27 -0
  269. package/template/src/client/components/AuthButton.tsx +41 -0
  270. package/template/src/client/components/ConnectionStatus.tsx +75 -0
  271. package/template/src/client/components/EmptyState.tsx +26 -0
  272. package/template/src/client/components/Footer.tsx +17 -0
  273. package/template/src/client/components/LoadingSpinner.tsx +26 -0
  274. package/template/src/client/components/MessageCard.tsx +61 -0
  275. package/template/src/client/components/Navigation.tsx +65 -0
  276. package/template/src/client/components/StatusBadge.tsx +50 -0
  277. package/template/src/client/components/__tests__/App.test.tsx +79 -0
  278. package/template/src/client/components/__tests__/AuthButton.test.tsx +119 -0
  279. package/template/src/client/components/__tests__/ConnectionStatus.test.tsx +188 -0
  280. package/template/src/client/components/__tests__/EmptyState.test.tsx +70 -0
  281. package/template/src/client/components/__tests__/Footer.test.tsx +22 -0
  282. package/template/src/client/components/__tests__/LoadingSpinner.test.tsx +75 -0
  283. package/template/src/client/components/__tests__/MessageCard.test.tsx +142 -0
  284. package/template/src/client/components/__tests__/Navigation.test.tsx +40 -0
  285. package/template/src/client/components/__tests__/StatusBadge.test.tsx +107 -0
  286. package/template/src/client/components/index.ts +6 -0
  287. package/template/src/client/index.css +27 -0
  288. package/template/src/client/main.tsx +25 -0
  289. package/template/src/client/pages/ContentDetailPage.tsx +142 -0
  290. package/template/src/client/pages/ContentListPage.tsx +169 -0
  291. package/template/src/client/pages/NotificationPage.tsx +288 -0
  292. package/template/src/client/pages/TodoPage.tsx +389 -0
  293. package/template/src/client/pages/WebSocketPage.tsx +239 -0
  294. package/template/src/client/pages/__tests__/ContentDetailPage.test.tsx +176 -0
  295. package/template/src/client/pages/__tests__/ContentListPage.test.tsx +202 -0
  296. package/template/src/client/pages/__tests__/NotificationPage.test.tsx +246 -0
  297. package/template/src/client/pages/__tests__/TodoPage.test.tsx +433 -0
  298. package/template/src/client/pages/__tests__/WebSocketPage.test.tsx +378 -0
  299. package/template/src/client/services/apiClient.ts +66 -0
  300. package/template/src/client/stores/__tests__/authStore.test.ts +139 -0
  301. package/template/src/client/stores/__tests__/chatWSStore.test.ts +416 -0
  302. package/template/src/client/stores/__tests__/notificationStore.test.ts +453 -0
  303. package/template/src/client/stores/__tests__/todoStore.test.ts +509 -0
  304. package/template/src/client/stores/authStore.ts +51 -0
  305. package/template/src/client/stores/chatWSStore.ts +122 -0
  306. package/template/src/client/stores/notificationStore.ts +199 -0
  307. package/template/src/client/stores/todoStore.ts +207 -0
  308. package/template/src/server/__tests__/integration/todos-api.test.ts +138 -0
  309. package/template/src/server/app.ts +105 -0
  310. package/template/src/server/config.ts +86 -0
  311. package/template/src/server/core/__tests__/realtime-core.test.ts +329 -0
  312. package/template/src/server/core/__tests__/realtime-scanner.test.ts +210 -0
  313. package/template/src/server/core/__tests__/runtime-node.test.ts +330 -0
  314. package/template/src/server/core/__tests__/runtime.test.ts +219 -0
  315. package/template/src/server/core/__tests__/typed-runtime.test.ts +178 -0
  316. package/template/src/server/core/durable-objects/RealtimeDO.ts +206 -0
  317. package/template/src/server/core/index.ts +83 -0
  318. package/template/src/server/core/module-loader.ts +281 -0
  319. package/template/src/server/core/realtime-core.ts +152 -0
  320. package/template/src/server/core/realtime-scanner.ts +99 -0
  321. package/template/src/server/core/runtime-cloudflare.ts +207 -0
  322. package/template/src/server/core/runtime-node.ts +170 -0
  323. package/template/src/server/core/runtime.ts +129 -0
  324. package/template/src/server/core/typed-runtime.ts +80 -0
  325. package/template/src/server/db/__tests__/driver.test.ts +203 -0
  326. package/template/src/server/db/config.ts +1 -0
  327. package/template/src/server/db/driver-cloudflare.ts +50 -0
  328. package/template/src/server/db/driver.ts +116 -0
  329. package/template/src/server/db/index.ts +3 -0
  330. package/template/src/server/db/init.ts +412 -0
  331. package/template/src/server/db/schema/api-endpoints.ts +23 -0
  332. package/template/src/server/db/schema/contents.ts +31 -0
  333. package/template/src/server/db/schema/disputes.ts +39 -0
  334. package/template/src/server/db/schema/index.ts +14 -0
  335. package/template/src/server/db/schema/notifications.ts +16 -0
  336. package/template/src/server/db/schema/orders.ts +30 -0
  337. package/template/src/server/db/schema/permission-audit-logs.ts +17 -0
  338. package/template/src/server/db/schema/permission-route-mappings.ts +22 -0
  339. package/template/src/server/db/schema/permissions.ts +17 -0
  340. package/template/src/server/db/schema/role-permissions.ts +22 -0
  341. package/template/src/server/db/schema/roles.ts +17 -0
  342. package/template/src/server/db/schema/tickets.ts +62 -0
  343. package/template/src/server/db/schema/todo-attachments.ts +22 -0
  344. package/template/src/server/db/schema/todos.ts +21 -0
  345. package/template/src/server/db/schema/user-roles.ts +17 -0
  346. package/template/src/server/db/seeds/index.ts +1 -0
  347. package/template/src/server/db/seeds/permission-data.ts +354 -0
  348. package/template/src/server/db/test-setup.ts +647 -0
  349. package/template/src/server/entries/cloudflare.ts +71 -0
  350. package/template/src/server/entries/node.ts +190 -0
  351. package/template/src/server/index.ts +62 -0
  352. package/template/src/server/middleware/__tests__/auth-simple.test.ts +147 -0
  353. package/template/src/server/middleware/__tests__/auth.test.ts +276 -0
  354. package/template/src/server/middleware/__tests__/captcha.test.ts +258 -0
  355. package/template/src/server/middleware/__tests__/error-handler.test.ts +364 -0
  356. package/template/src/server/middleware/__tests__/error-response-format.test.ts +80 -0
  357. package/template/src/server/middleware/__tests__/permission.test.ts +312 -0
  358. package/template/src/server/middleware/audit-log.ts +101 -0
  359. package/template/src/server/middleware/auth.ts +235 -0
  360. package/template/src/server/middleware/captcha.ts +166 -0
  361. package/template/src/server/middleware/cors.ts +30 -0
  362. package/template/src/server/middleware/error-handler.ts +135 -0
  363. package/template/src/server/middleware/index.ts +24 -0
  364. package/template/src/server/middleware/logger.ts +73 -0
  365. package/template/src/server/middleware/permission.ts +127 -0
  366. package/template/src/server/middleware/rate-limit.ts +69 -0
  367. package/template/src/server/middleware/realtime-env.ts +12 -0
  368. package/template/src/server/module-admin/__tests__/admin-routes.test.ts +360 -0
  369. package/template/src/server/module-admin/__tests__/admin-service.test.ts +222 -0
  370. package/template/src/server/module-admin/__tests__/audit-log-routes.test.ts +261 -0
  371. package/template/src/server/module-admin/__tests__/export-routes.test.ts +160 -0
  372. package/template/src/server/module-admin/__tests__/media-routes.test.ts +122 -0
  373. package/template/src/server/module-admin/module.ts +40 -0
  374. package/template/src/server/module-admin/routes/admin-notification-routes.ts +197 -0
  375. package/template/src/server/module-admin/routes/admin-routes.ts +20 -0
  376. package/template/src/server/module-admin/routes/auth-routes.ts +86 -0
  377. package/template/src/server/module-admin/routes/export-routes.ts +207 -0
  378. package/template/src/server/module-admin/routes/media-routes.ts +78 -0
  379. package/template/src/server/module-admin/routes/system-routes.ts +89 -0
  380. package/template/src/server/module-admin/routes/user-management-routes.ts +143 -0
  381. package/template/src/server/module-admin/services/admin-service.ts +268 -0
  382. package/template/src/server/module-captcha/__tests__/captcha-route.test.ts +35 -0
  383. package/template/src/server/module-captcha/__tests__/captcha-service.test.ts +53 -0
  384. package/template/src/server/module-captcha/module.ts +35 -0
  385. package/template/src/server/module-captcha/routes/captcha-routes.ts +68 -0
  386. package/template/src/server/module-captcha/services/captcha-service.ts +3 -0
  387. package/template/src/server/module-chat/__tests__/chat-routes.test.ts +39 -0
  388. package/template/src/server/module-chat/__tests__/chat-rpc.test.ts +185 -0
  389. package/template/src/server/module-chat/__tests__/chat-service.test.ts +64 -0
  390. package/template/src/server/module-chat/module.ts +27 -0
  391. package/template/src/server/module-chat/routes/chat-routes.ts +46 -0
  392. package/template/src/server/module-chat/services/chat-service.ts +38 -0
  393. package/template/src/server/module-content/__tests__/content-route.test.ts +40 -0
  394. package/template/src/server/module-content/__tests__/content-service.test.ts +107 -0
  395. package/template/src/server/module-content/module.ts +39 -0
  396. package/template/src/server/module-content/routes/content-routes.ts +182 -0
  397. package/template/src/server/module-content/routes/public-content-routes.ts +58 -0
  398. package/template/src/server/module-content/services/content-service.ts +224 -0
  399. package/template/src/server/module-dispute/__tests__/dispute-route.test.ts +43 -0
  400. package/template/src/server/module-dispute/__tests__/dispute-service.test.ts +135 -0
  401. package/template/src/server/module-dispute/module.ts +30 -0
  402. package/template/src/server/module-dispute/routes/dispute-routes.ts +153 -0
  403. package/template/src/server/module-dispute/services/dispute-service.ts +255 -0
  404. package/template/src/server/module-file/__tests__/file-routes.test.ts +284 -0
  405. package/template/src/server/module-file/__tests__/file-storage-service.test.ts +323 -0
  406. package/template/src/server/module-file/module.ts +28 -0
  407. package/template/src/server/module-file/routes/file-routes.ts +288 -0
  408. package/template/src/server/module-notifications/__tests__/notification-route-rpc.test.ts +375 -0
  409. package/template/src/server/module-notifications/__tests__/notification-service.test.ts +241 -0
  410. package/template/src/server/module-notifications/__tests__/sse-rpc.test.ts +289 -0
  411. package/template/src/server/module-notifications/module.ts +32 -0
  412. package/template/src/server/module-notifications/routes/notification-routes.ts +190 -0
  413. package/template/src/server/module-notifications/services/notification-service.ts +104 -0
  414. package/template/src/server/module-order/__tests__/order-route.test.ts +258 -0
  415. package/template/src/server/module-order/__tests__/order-service.test.ts +222 -0
  416. package/template/src/server/module-order/module.ts +30 -0
  417. package/template/src/server/module-order/routes/order-routes.ts +202 -0
  418. package/template/src/server/module-order/services/order-service.ts +268 -0
  419. package/template/src/server/module-permission/__tests__/permission-middleware.test.ts +90 -0
  420. package/template/src/server/module-permission/__tests__/permission-routes.test.ts +330 -0
  421. package/template/src/server/module-permission/__tests__/permission-service-impl.test.ts +388 -0
  422. package/template/src/server/module-permission/__tests__/permission-service.test.ts +190 -0
  423. package/template/src/server/module-permission/__tests__/role-routes.test.ts +215 -0
  424. package/template/src/server/module-permission/module.ts +68 -0
  425. package/template/src/server/module-permission/routes/audit-log-routes.ts +76 -0
  426. package/template/src/server/module-permission/routes/permission-routes.ts +184 -0
  427. package/template/src/server/module-permission/routes/role-routes.ts +263 -0
  428. package/template/src/server/module-permission/services/audit-log-service.ts +108 -0
  429. package/template/src/server/module-permission/services/permission-service-impl.ts +233 -0
  430. package/template/src/server/module-permission/services/permission-service.ts +256 -0
  431. package/template/src/server/module-permission/services/role-service.ts +122 -0
  432. package/template/src/server/module-ticket/__tests__/ticket-route.test.ts +173 -0
  433. package/template/src/server/module-ticket/__tests__/ticket-service.test.ts +201 -0
  434. package/template/src/server/module-ticket/module.ts +30 -0
  435. package/template/src/server/module-ticket/routes/ticket-routes.ts +210 -0
  436. package/template/src/server/module-ticket/services/ticket-service.ts +297 -0
  437. package/template/src/server/module-todos/__tests__/todo-service.test.ts +196 -0
  438. package/template/src/server/module-todos/__tests__/todos-file-upload.test.ts +439 -0
  439. package/template/src/server/module-todos/__tests__/todos-route-rpc.test.ts +575 -0
  440. package/template/src/server/module-todos/module.ts +30 -0
  441. package/template/src/server/module-todos/routes/todos-routes.ts +260 -0
  442. package/template/src/server/module-todos/services/todo-service.ts +202 -0
  443. package/template/src/server/route-registry.ts +47 -0
  444. package/template/src/server/test-utils/test-client.ts +66 -0
  445. package/template/src/server/test-utils/test-server.ts +131 -0
  446. package/template/src/server/types/bindings.ts +10 -0
  447. package/template/src/server/utils/__tests__/app-error.test.ts +374 -0
  448. package/template/src/server/utils/__tests__/auth.test.ts +76 -0
  449. package/template/src/server/utils/__tests__/captcha.test.ts +80 -0
  450. package/template/src/server/utils/__tests__/file-storage.test.ts +472 -0
  451. package/template/src/server/utils/__tests__/logger.test.ts +221 -0
  452. package/template/src/server/utils/__tests__/route-helpers.test.ts +264 -0
  453. package/template/src/server/utils/app-error.ts +355 -0
  454. package/template/src/server/utils/auth.ts +65 -0
  455. package/template/src/server/utils/captcha.ts +119 -0
  456. package/template/src/server/utils/date.ts +50 -0
  457. package/template/src/server/utils/env.ts +4 -0
  458. package/template/src/server/utils/file-storage.ts +462 -0
  459. package/template/src/server/utils/generate.ts +26 -0
  460. package/template/src/server/utils/id-helpers.ts +5 -0
  461. package/template/src/server/utils/logger.ts +139 -0
  462. package/template/src/server/utils/permission-utils.ts +6 -0
  463. package/template/src/server/utils/response.ts +40 -0
  464. package/template/src/server/utils/route-helpers.ts +200 -0
  465. package/template/src/server/utils/uuid.ts +8 -0
  466. package/template/src/server/utils/ws-helper.ts +30 -0
  467. package/template/src/shared/constants/index.ts +1 -0
  468. package/template/src/shared/constants/resource-types.ts +72 -0
  469. package/template/src/shared/core/__tests__/sse-client.test.ts +376 -0
  470. package/template/src/shared/core/__tests__/ws-client.test.ts +468 -0
  471. package/template/src/shared/core/api-request.ts +192 -0
  472. package/template/src/shared/core/api-schemas.ts +41 -0
  473. package/template/src/shared/core/index.ts +37 -0
  474. package/template/src/shared/core/module-manifest.ts +120 -0
  475. package/template/src/shared/core/protocol-types.ts +26 -0
  476. package/template/src/shared/core/sse-client.ts +185 -0
  477. package/template/src/shared/core/ws-client.ts +236 -0
  478. package/template/src/shared/hooks/index.ts +2 -0
  479. package/template/src/shared/hooks/useSSE.ts +57 -0
  480. package/template/src/shared/hooks/useWebSocket.ts +66 -0
  481. package/template/src/shared/index.ts +5 -0
  482. package/template/src/shared/modules/admin/index.ts +1 -0
  483. package/template/src/shared/modules/admin/schemas.ts +100 -0
  484. package/template/src/shared/modules/audit/index.ts +1 -0
  485. package/template/src/shared/modules/audit/schemas.ts +24 -0
  486. package/template/src/shared/modules/captcha/index.ts +1 -0
  487. package/template/src/shared/modules/captcha/schemas.ts +19 -0
  488. package/template/src/shared/modules/chat/index.ts +38 -0
  489. package/template/src/shared/modules/content/index.ts +1 -0
  490. package/template/src/shared/modules/content/schemas.ts +53 -0
  491. package/template/src/shared/modules/dispute/index.ts +1 -0
  492. package/template/src/shared/modules/dispute/schemas.ts +62 -0
  493. package/template/src/shared/modules/files/index.ts +11 -0
  494. package/template/src/shared/modules/files/schemas.ts +44 -0
  495. package/template/src/shared/modules/index.ts +70 -0
  496. package/template/src/shared/modules/notifications/index.ts +26 -0
  497. package/template/src/shared/modules/notifications/schemas.ts +73 -0
  498. package/template/src/shared/modules/order/index.ts +1 -0
  499. package/template/src/shared/modules/order/schemas.ts +63 -0
  500. package/template/src/shared/modules/permission/index.ts +42 -0
  501. package/template/src/shared/modules/permission/permission-dependencies.ts +50 -0
  502. package/template/src/shared/modules/permission/permissions.ts +275 -0
  503. package/template/src/shared/modules/permission/schemas.ts +92 -0
  504. package/template/src/shared/modules/permission/types.ts +23 -0
  505. package/template/src/shared/modules/role/index.ts +1 -0
  506. package/template/src/shared/modules/role/schemas.ts +46 -0
  507. package/template/src/shared/modules/ticket/index.ts +1 -0
  508. package/template/src/shared/modules/ticket/schemas.ts +77 -0
  509. package/template/src/shared/modules/todos/index.ts +20 -0
  510. package/template/src/shared/modules/todos/schemas.ts +65 -0
  511. package/template/src/shared/schemas/index.ts +79 -0
  512. package/template/src/types/global.d.ts +14 -0
  513. package/template/src/vite-env.d.ts +21 -0
  514. package/template/tailwind.config.js +11 -0
  515. package/template/tests/e2e/admin-crud.spec.ts +81 -0
  516. package/template/tests/e2e/case-o1.spec.ts +108 -0
  517. package/template/tests/e2e/global-setup.ts +73 -0
  518. package/template/tests/e2e/global-teardown.ts +28 -0
  519. package/template/tests/e2e/notification.spec.ts +458 -0
  520. package/template/tests/e2e/todo.spec.ts +623 -0
  521. package/template/tests/e2e/websocket.spec.ts +254 -0
  522. package/template/tsconfig.json +35 -0
  523. package/template/tsup.config.ts +95 -0
  524. package/template/vite-plugins.ts +113 -0
  525. package/template/vite.config.ts +96 -0
  526. package/template/vitest.config.ts +53 -0
  527. package/template/vitest.integration.config.ts +24 -0
  528. package/template/vitest.integration.setup.ts +22 -0
  529. package/template/vitest.setup.ts +74 -0
  530. package/template/wrangler.toml +33 -0
@@ -0,0 +1,623 @@
1
+ /**
2
+ * Todo App E2E Tests
3
+ *
4
+ * Testing Todo application functionality with Playwright
5
+ *
6
+ * The dev server is started automatically by global-setup.ts
7
+ * on a random available port.
8
+ */
9
+
10
+ import { test, expect } from '@playwright/test'
11
+
12
+ function getBaseUrl(): string {
13
+ return process.env.PLAYWRIGHT_TEST_BASE_URL || 'http://localhost:3010'
14
+ }
15
+
16
+ test.beforeEach(async ({ page }) => {
17
+ // 1. Cleanup database
18
+ try {
19
+ const response = await page.request.post(`${getBaseUrl()}/api/__test__/cleanup`)
20
+ if (!response.ok) {
21
+ console.warn('Failed to cleanup database:', await response.text())
22
+ }
23
+ } catch (error) {
24
+ console.warn('Error during database cleanup:', error)
25
+ }
26
+
27
+ // 2. Navigate to app first to establish correct origin for localStorage
28
+ await page.goto(getBaseUrl())
29
+ await page.waitForLoadState('load')
30
+
31
+ // 3. Clear storage (now on correct origin)
32
+ try {
33
+ await page.evaluate(() => {
34
+ try {
35
+ localStorage.clear()
36
+ sessionStorage.clear()
37
+ } catch (e) {
38
+ console.warn('Could not clear storage:', e)
39
+ }
40
+ })
41
+ } catch (error) {
42
+ console.warn('Error clearing storage:', error)
43
+ }
44
+
45
+ // 4. Set auth token (now on correct origin, persists across reloads)
46
+ await page.evaluate(() => {
47
+ localStorage.setItem('auth-token', JSON.stringify('user-token'))
48
+ })
49
+ })
50
+
51
+ test.afterEach(async ({ page, context }) => {
52
+ // Close all pages in context
53
+ const pages = context.pages()
54
+ for (const p of pages) {
55
+ if (p !== page) {
56
+ await p.close()
57
+ }
58
+ }
59
+ })
60
+
61
+ test.describe('Todo App', () => {
62
+ /**
63
+ * Test 1: Page Load
64
+ */
65
+ test.describe('Page Load', () => {
66
+ test('should load homepage successfully', async ({ page }) => {
67
+ // Navigate to homepage
68
+ await page.goto(getBaseUrl())
69
+
70
+ // Wait for page to load
71
+ await page.waitForLoadState('load')
72
+
73
+ // Wait for network to be idle
74
+ await page.waitForLoadState('networkidle')
75
+
76
+ // Wait for the app to render
77
+ await page.waitForSelector('[data-testid="app-container"]', { timeout: 25000 })
78
+
79
+ // Verify main container is visible
80
+ await expect(page.locator('[data-testid="app-container"]')).toBeVisible()
81
+ })
82
+
83
+ test('should display empty state when no todos', async ({ page }) => {
84
+ // Navigate to homepage
85
+ await page.goto(getBaseUrl())
86
+
87
+ // Wait for page to load
88
+ await page.waitForLoadState('load')
89
+
90
+ // Wait for network to be idle
91
+ await page.waitForLoadState('networkidle')
92
+
93
+ // Wait for the app to render
94
+ await page.waitForSelector('[data-testid="app-container"]', { timeout: 25000 })
95
+
96
+ // Wait for empty state to appear
97
+ await page.waitForSelector('[data-testid="empty-state"]', { timeout: 25000 })
98
+
99
+ // Verify empty state message
100
+ await expect(page.locator('[data-testid="empty-state"]')).toBeVisible()
101
+ })
102
+ })
103
+
104
+ /**
105
+ * Test 2: Create Todo
106
+ */
107
+ test.describe('Create Todo', () => {
108
+ test('should create a new todo with title only', async ({ page }) => {
109
+ // Navigate to homepage
110
+ await page.goto(getBaseUrl())
111
+
112
+ // Wait for page to load
113
+ await page.waitForLoadState('load')
114
+
115
+ // Wait for network to be idle
116
+ await page.waitForLoadState('networkidle')
117
+
118
+ // Fill in todo title with specific value
119
+ await page.fill('[data-testid="todo-title-input"]', 'Buy groceries')
120
+
121
+ // Submit form
122
+ await page.click('[data-testid="add-todo-button"]')
123
+
124
+ // Wait for network to be idle
125
+ await page.waitForLoadState('networkidle')
126
+
127
+ // Verify todo is created with specific data
128
+ await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(1)
129
+ await expect(
130
+ page.locator('[data-testid="todo-item"] [data-testid="todo-item-title"]').first()
131
+ ).toHaveText('Buy groceries')
132
+
133
+ // Verify todo has correct status
134
+ await expect(
135
+ page.locator('[data-testid="todo-item"]').first().locator('[data-testid="todo-status"]')
136
+ ).toHaveValue('pending')
137
+ })
138
+
139
+ test('should create a new todo with title and description', async ({ page }) => {
140
+ // Navigate to homepage
141
+ await page.goto(getBaseUrl())
142
+
143
+ // Wait for page to load
144
+ await page.waitForLoadState('load')
145
+
146
+ // Wait for network to be idle
147
+ await page.waitForLoadState('networkidle')
148
+
149
+ // Fill in both title and description
150
+ await page.fill('[data-testid="todo-title-input"]', 'Clean house')
151
+ await page.fill('[data-testid="todo-description-input"]', 'Clean living room and kitchen')
152
+
153
+ // Submit form
154
+ await page.click('[data-testid="add-todo-button"]')
155
+
156
+ // Wait for network to be idle
157
+ await page.waitForLoadState('networkidle')
158
+
159
+ // Verify todo is created
160
+ await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(1)
161
+ await expect(
162
+ page.locator('[data-testid="todo-item"] [data-testid="todo-item-title"]').first()
163
+ ).toHaveText('Clean house')
164
+ })
165
+
166
+ test('should clear input after creating todo', async ({ page }) => {
167
+ // Navigate to homepage
168
+ await page.goto(getBaseUrl())
169
+
170
+ // Wait for page to load
171
+ await page.waitForLoadState('load')
172
+
173
+ // Wait for network to be idle
174
+ await page.waitForLoadState('networkidle')
175
+
176
+ // Fill and submit
177
+ await page.fill('[data-testid="todo-title-input"]', 'Test todo')
178
+ await page.click('[data-testid="add-todo-button"]')
179
+
180
+ // Wait for network to be idle
181
+ await page.waitForLoadState('networkidle')
182
+
183
+ // Verify input is cleared
184
+ await expect(page.locator('[data-testid="todo-title-input"]')).toHaveValue('')
185
+ })
186
+
187
+ test('should not create empty todo', async ({ page }) => {
188
+ // Navigate to homepage
189
+ await page.goto(getBaseUrl())
190
+
191
+ // Wait for page to load
192
+ await page.waitForLoadState('load')
193
+
194
+ // Wait for network to be idle
195
+ await page.waitForLoadState('networkidle')
196
+
197
+ // Get initial todo count
198
+ const initialCount = await page.locator('[data-testid="todo-item"]').count()
199
+
200
+ // Verify add todo button is disabled when input is empty
201
+ await expect(page.locator('[data-testid="add-todo-button"]')).toBeDisabled()
202
+
203
+ // Verify no new todo was created
204
+ await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(initialCount)
205
+ })
206
+ })
207
+
208
+ /**
209
+ * Test 3: Update Todo Status
210
+ */
211
+ test.describe('Update Todo', () => {
212
+ test.beforeEach(async ({ page }) => {
213
+ // Navigate to homepage
214
+ await page.goto(getBaseUrl())
215
+
216
+ // Wait for page to load
217
+ await page.waitForLoadState('load')
218
+
219
+ // Wait for network to be idle
220
+ await page.waitForLoadState('networkidle')
221
+
222
+ // Create a test todo
223
+ await page.fill('[data-testid="todo-title-input"]', 'Test Todo')
224
+ await page.click('[data-testid="add-todo-button"]')
225
+
226
+ // Wait for network to be idle
227
+ await page.waitForLoadState('networkidle')
228
+ })
229
+
230
+ test('should change todo status to completed', async ({ page }) => {
231
+ // Wait for the todo item to be visible
232
+ await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(1)
233
+
234
+ // Change status via select element scoped to the first todo item
235
+ await page
236
+ .locator('[data-testid="todo-item"]')
237
+ .first()
238
+ .locator('[data-testid="todo-status"]')
239
+ .selectOption('completed')
240
+
241
+ // Wait for network to be idle
242
+ await page.waitForLoadState('networkidle')
243
+
244
+ // Verify status changed to completed
245
+ await expect(
246
+ page.locator('[data-testid="todo-item"]').first().locator('[data-testid="todo-status"]')
247
+ ).toHaveValue('completed')
248
+ })
249
+
250
+ test('should change todo status back to pending', async ({ page }) => {
251
+ // Wait for the todo item to be visible
252
+ await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(1)
253
+
254
+ const statusSelect = page
255
+ .locator('[data-testid="todo-item"]')
256
+ .first()
257
+ .locator('[data-testid="todo-status"]')
258
+
259
+ // Change to completed first
260
+ await statusSelect.selectOption('completed')
261
+
262
+ // Wait for network to be idle
263
+ await page.waitForLoadState('networkidle')
264
+
265
+ // Change back to pending
266
+ await statusSelect.selectOption('pending')
267
+
268
+ // Wait for network to be idle
269
+ await page.waitForLoadState('networkidle')
270
+
271
+ // Verify status is back to pending
272
+ await expect(statusSelect).toHaveValue('pending')
273
+ })
274
+ })
275
+
276
+ /**
277
+ * Test 4: Delete Todo
278
+ */
279
+ test.describe('Delete Todo', () => {
280
+ test('should delete a todo', async ({ page }) => {
281
+ // Navigate to homepage
282
+ await page.goto(getBaseUrl())
283
+
284
+ // Wait for page to load
285
+ await page.waitForLoadState('load')
286
+
287
+ // Wait for network to be idle
288
+ await page.waitForLoadState('networkidle')
289
+
290
+ // Create a test todo
291
+ await page.fill('[data-testid="todo-title-input"]', 'Todo to delete')
292
+ await page.click('[data-testid="add-todo-button"]')
293
+
294
+ // Wait for network to be idle
295
+ await page.waitForLoadState('networkidle')
296
+
297
+ // Verify todo was created
298
+ await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(1)
299
+
300
+ // Click delete button
301
+ await page.click('[data-testid="todo-item"] [data-testid="delete-button"]')
302
+
303
+ // Wait for the delete to complete - wait for todo to be removed
304
+ await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(0, { timeout: 10000 })
305
+
306
+ // Verify empty state is shown
307
+ await expect(page.locator('[data-testid="empty-state"]')).toBeVisible()
308
+ })
309
+ })
310
+
311
+ /**
312
+ * Test 5: Filter Todos
313
+ */
314
+ test.describe('Filter Todos', () => {
315
+ test.beforeEach(async ({ page }) => {
316
+ // Navigate to homepage
317
+ await page.goto(getBaseUrl())
318
+
319
+ // Wait for page to load
320
+ await page.waitForLoadState('load')
321
+
322
+ // Wait for network to be idle
323
+ await page.waitForLoadState('networkidle')
324
+
325
+ // Create multiple todos with different statuses
326
+ await page.fill('[data-testid="todo-title-input"]', 'Todo 1')
327
+ // Wait for input to be processed
328
+ await page.waitForTimeout(500)
329
+ await expect(page.locator('[data-testid="add-todo-button"]')).toBeEnabled()
330
+ await page.click('[data-testid="add-todo-button"]')
331
+
332
+ // Wait for network to be idle
333
+ await page.waitForLoadState('networkidle')
334
+
335
+ // Wait for form to reset
336
+ await page.waitForTimeout(500)
337
+ await page.fill('[data-testid="todo-title-input"]', 'Todo 2')
338
+ // Wait for input to be processed
339
+ await page.waitForTimeout(500)
340
+ await expect(page.locator('[data-testid="add-todo-button"]')).toBeEnabled()
341
+ await page.click('[data-testid="add-todo-button"]')
342
+
343
+ // Wait for network to be idle
344
+ await page.waitForLoadState('networkidle')
345
+
346
+ // Mark first todo as completed
347
+ await page.selectOption('[data-testid="todo-item"] [data-testid="todo-status"]', 'completed')
348
+
349
+ // Wait for network to be idle
350
+ await page.waitForLoadState('networkidle')
351
+ })
352
+
353
+ test('should filter to show only pending todos', async ({ page }) => {
354
+ // Click filter to show pending only
355
+ await page.click('[data-testid="filter-pending"]')
356
+
357
+ // Wait for network to be idle
358
+ await page.waitForLoadState('networkidle')
359
+
360
+ // Verify only pending todos are shown
361
+ await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(1)
362
+ })
363
+
364
+ test('should filter to show only completed todos', async ({ page }) => {
365
+ // Click filter to show completed only
366
+ await page.click('[data-testid="filter-completed"]')
367
+
368
+ // Wait for network to be idle
369
+ await page.waitForLoadState('networkidle')
370
+
371
+ // Verify only completed todos are shown
372
+ await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(1)
373
+ })
374
+
375
+ test('should show all todos when filter is reset', async ({ page }) => {
376
+ // Filter to completed
377
+ await page.click('[data-testid="filter-completed"]')
378
+
379
+ // Wait for network to be idle
380
+ await page.waitForLoadState('networkidle')
381
+
382
+ // Reset filter to all
383
+ await page.click('[data-testid="filter-all"]')
384
+
385
+ // Wait for network to be idle
386
+ await page.waitForLoadState('networkidle')
387
+
388
+ // Verify all todos are shown
389
+ await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(2)
390
+ })
391
+ })
392
+
393
+ /**
394
+ * Test 6: Multiple Todos
395
+ */
396
+ test.describe('Multiple Todos', () => {
397
+ test('should display multiple todos in correct order', async ({ page }) => {
398
+ // Navigate to homepage
399
+ await page.goto(getBaseUrl())
400
+
401
+ // Wait for page to load
402
+ await page.waitForLoadState('load')
403
+
404
+ // Wait for network to be idle
405
+ await page.waitForLoadState('networkidle')
406
+
407
+ // Create multiple todos
408
+ await page.fill('[data-testid="todo-title-input"]', 'First todo')
409
+ // Wait for input to be processed
410
+ await page.waitForTimeout(500)
411
+ await expect(page.locator('[data-testid="add-todo-button"]')).toBeEnabled()
412
+ await page.click('[data-testid="add-todo-button"]')
413
+
414
+ // Wait for network to be idle
415
+ await page.waitForLoadState('networkidle')
416
+
417
+ // Wait for form to reset
418
+ await page.waitForTimeout(500)
419
+ await page.fill('[data-testid="todo-title-input"]', 'Second todo')
420
+ // Wait for input to be processed
421
+ await page.waitForTimeout(500)
422
+ await expect(page.locator('[data-testid="add-todo-button"]')).toBeEnabled()
423
+ await page.click('[data-testid="add-todo-button"]')
424
+
425
+ // Wait for network to be idle
426
+ await page.waitForLoadState('networkidle')
427
+
428
+ // Wait for form to reset
429
+ await page.waitForTimeout(500)
430
+ await page.fill('[data-testid="todo-title-input"]', 'Third todo')
431
+ // Wait for input to be processed
432
+ await page.waitForTimeout(500)
433
+ await expect(page.locator('[data-testid="add-todo-button"]')).toBeEnabled()
434
+ await page.click('[data-testid="add-todo-button"]')
435
+
436
+ // Wait for network to be idle
437
+ await page.waitForLoadState('networkidle')
438
+
439
+ // Verify all todos are displayed
440
+ await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(3)
441
+
442
+ // Verify order (newest first)
443
+ await expect(
444
+ page.locator('[data-testid="todo-item"] [data-testid="todo-item-title"]').nth(0)
445
+ ).toHaveText('Third todo')
446
+ await expect(
447
+ page.locator('[data-testid="todo-item"] [data-testid="todo-item-title"]').nth(1)
448
+ ).toHaveText('Second todo')
449
+ await expect(
450
+ page.locator('[data-testid="todo-item"] [data-testid="todo-item-title"]').nth(2)
451
+ ).toHaveText('First todo')
452
+ })
453
+
454
+ test('should display todo count', async ({ page }) => {
455
+ // Navigate to homepage
456
+ await page.goto(getBaseUrl())
457
+
458
+ // Wait for page to load
459
+ await page.waitForLoadState('load')
460
+
461
+ // Wait for network to be idle
462
+ await page.waitForLoadState('networkidle')
463
+
464
+ // Create multiple todos
465
+ await page.fill('[data-testid="todo-title-input"]', 'Todo 1')
466
+ // Wait for input to be processed
467
+ await page.waitForTimeout(500)
468
+ await expect(page.locator('[data-testid="add-todo-button"]')).toBeEnabled()
469
+ await page.click('[data-testid="add-todo-button"]')
470
+
471
+ // Wait for network to be idle
472
+ await page.waitForLoadState('networkidle')
473
+
474
+ // Wait for form to reset
475
+ await page.waitForTimeout(500)
476
+ await page.fill('[data-testid="todo-title-input"]', 'Todo 2')
477
+ // Wait for input to be processed
478
+ await page.waitForTimeout(500)
479
+ await expect(page.locator('[data-testid="add-todo-button"]')).toBeEnabled()
480
+ await page.click('[data-testid="add-todo-button"]')
481
+
482
+ // Wait for network to be idle
483
+ await page.waitForLoadState('networkidle')
484
+
485
+ // Verify todo count
486
+ await expect(page.locator('[data-testid="todo-count"]')).toHaveText(/Total:2/)
487
+ })
488
+ })
489
+
490
+ /**
491
+ * Test 7: Persistence
492
+ */
493
+ test.describe('Persistence', () => {
494
+ test('should persist todos across page reloads', async ({ page, browser }) => {
495
+ // Cleanup database first
496
+ try {
497
+ const response = await page.request.post(`${getBaseUrl()}/api/__test__/cleanup`)
498
+ if (!response.ok) {
499
+ console.warn('Failed to cleanup database:', await response.text())
500
+ }
501
+ } catch (error) {
502
+ console.warn('Error during database cleanup:', error)
503
+ }
504
+
505
+ // Navigate to homepage
506
+ await page.goto(getBaseUrl())
507
+
508
+ // Wait for page to load
509
+ await page.waitForLoadState('load')
510
+
511
+ // Wait for network to be idle
512
+ await page.waitForLoadState('networkidle')
513
+
514
+ // Wait for app to render
515
+ await page.waitForSelector('[data-testid="todo-form"]', { timeout: 15000 })
516
+
517
+ // Create a todo
518
+ await page.fill('[data-testid="todo-title-input"]', 'Persistent todo')
519
+ await expect(page.locator('[data-testid="add-todo-button"]')).toBeEnabled()
520
+ await page.click('[data-testid="add-todo-button"]')
521
+
522
+ // Wait for network to be idle
523
+ await page.waitForLoadState('networkidle')
524
+
525
+ // Verify todo was created
526
+ await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(1)
527
+
528
+ const newContext = await browser.newContext()
529
+ const newPage = await newContext.newPage()
530
+
531
+ try {
532
+ await newPage.goto(getBaseUrl())
533
+ await newPage.waitForLoadState('load')
534
+
535
+ await newPage.evaluate(() => {
536
+ localStorage.setItem('auth-token', JSON.stringify('user-token'))
537
+ })
538
+
539
+ await newPage.reload()
540
+ await newPage.waitForLoadState('load')
541
+ await newPage.waitForLoadState('networkidle')
542
+ await newPage.waitForSelector('[data-testid="todo-item"]', { timeout: 15000 })
543
+
544
+ // Verify todo still exists
545
+ await expect(newPage.locator('[data-testid="todo-item"]')).toHaveCount(1)
546
+ await expect(
547
+ newPage.locator('[data-testid="todo-item"] [data-testid="todo-item-title"]').first()
548
+ ).toHaveText('Persistent todo')
549
+ } finally {
550
+ // Cleanup
551
+ await newContext.close()
552
+ }
553
+ })
554
+ })
555
+
556
+ /**
557
+ * Test 8: Navigation
558
+ */
559
+ test.describe('Navigation', () => {
560
+ test('should filter todos correctly', async ({ page }) => {
561
+ // Navigate to homepage
562
+ await page.goto(getBaseUrl())
563
+
564
+ // Wait for page to load
565
+ await page.waitForLoadState('load')
566
+
567
+ // Wait for network to be idle
568
+ await page.waitForLoadState('networkidle')
569
+
570
+ // Create some todos
571
+ await page.fill('[data-testid="todo-title-input"]', 'Todo 1')
572
+ await page.click('[data-testid="add-todo-button"]')
573
+
574
+ // Wait for network to be idle
575
+ await page.waitForLoadState('networkidle')
576
+
577
+ // Wait for the todo item to be visible (ensures filter buttons render)
578
+ await expect(page.locator('[data-testid="todo-item"]')).toHaveCount(1)
579
+
580
+ // Navigate to completed filter
581
+ await page.click('[data-testid="filter-completed"]')
582
+
583
+ // Wait for network to be idle
584
+ await page.waitForLoadState('networkidle')
585
+
586
+ // Navigate back to all
587
+ await page.click('[data-testid="filter-all"]')
588
+
589
+ // Wait for network to be idle
590
+ await page.waitForLoadState('networkidle')
591
+ })
592
+ })
593
+ })
594
+
595
+ /**
596
+ * IMPORTANT: Resource Cleanup Notes
597
+ *
598
+ * Playwright automatically handles cleanup at the end of each test:
599
+ * - All pages in the context are closed
600
+ * - Browser contexts are closed
601
+ * - The browser is closed after all tests complete
602
+ *
603
+ * However, for long-running tests or custom scenarios:
604
+ * 1. Always use test.afterEach() for test-specific cleanup
605
+ * 2. Store page/browser references for explicit cleanup if needed
606
+ * 3. Use try/finally blocks to ensure cleanup even on test failure
607
+ *
608
+ * Example of explicit cleanup:
609
+ *
610
+ * test.afterEach(async ({ page, context }) => {
611
+ * // Close all pages in context
612
+ * const pages = context.pages();
613
+ * for (const p of pages) {
614
+ * await p.close().catch(() => {});
615
+ * }
616
+ *
617
+ * // Clear storage
618
+ * await page.evaluate(() => {
619
+ * localStorage.clear();
620
+ * sessionStorage.clear();
621
+ * });
622
+ * });
623
+ */