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.
- package/README.md +154 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +439 -0
- package/dist/cli/index.js.map +1 -0
- package/package.json +171 -0
- package/template/.agents/skills/writing-hookify-rules/SKILL.md +408 -0
- package/template/.ai-context.md +368 -0
- package/template/.claude/hookify.block-dangerous-commands.local.md +41 -0
- package/template/.claude/hookify.check-api-types.local.md +111 -0
- package/template/.claude/hookify.check-duplicate-types.local.md +287 -0
- package/template/.claude/hookify.check-test-assertions.local.md +93 -0
- package/template/.claude/hookify.remind-add-tests.local.md +209 -0
- package/template/.claude/hookify.remind-db-migration.local.md +58 -0
- package/template/.claude/hookify.remind-run-tests.local.md +52 -0
- package/template/.claude/hookify.warn-any-type.local.md +203 -0
- package/template/.claude/hookify.warn-console-log.local.md +73 -0
- package/template/.claude/rules/00-project-config.md +147 -0
- package/template/.claude/rules/01-file-types.md +127 -0
- package/template/.claude/rules/02-git-workflow.md +167 -0
- package/template/.claude/rules/10-api-type-inference.md +555 -0
- package/template/.claude/rules/20-server-api.md +349 -0
- package/template/.claude/rules/21-server-entrypoint.md +257 -0
- package/template/.claude/rules/30-client-components.md +314 -0
- package/template/.claude/rules/31-client-services.md +768 -0
- package/template/.claude/rules/32-client-state-zustand.md +324 -0
- package/template/.claude/rules/33-client-app-entry.md +276 -0
- package/template/.claude/rules/34-client-admin-module.md +445 -0
- package/template/.claude/rules/35-cli-module.md +259 -0
- package/template/.claude/rules/40-admin-module.md +495 -0
- package/template/.claude/rules/40-shared-types.md +450 -0
- package/template/.claude/rules/50-websocket.md +245 -0
- package/template/.claude/rules/51-sse.md +372 -0
- package/template/.claude/rules/52-cloudflare-realtime.md +384 -0
- package/template/.claude/rules/60-testing-standards.md +245 -0
- package/template/.claude/rules/61-hono-testing.md +200 -0
- package/template/.claude/scripts/README.md +187 -0
- package/template/.claude/scripts/check-duplicate-types.ts +188 -0
- package/template/.claude/scripts/post-edit-check-incremental.sh +61 -0
- package/template/.claude/scripts/post-edit-check.sh +22 -0
- package/template/.claude/scripts/pre-tool-use-check.sh +86 -0
- package/template/.claude/scripts/test-pre-tool-use.sh +73 -0
- package/template/.claude/scripts/validate-git-changes.sh +38 -0
- package/template/.claude/settings.json +32 -0
- package/template/.claude/skills/writing-hookify-rules/SKILL.md +408 -0
- package/template/.dev-context-compact.md +54 -0
- package/template/.dev-context.md +621 -0
- package/template/.dev-guide.md +243 -0
- package/template/.env.example +60 -0
- package/template/.eslintrc.routes.json +39 -0
- package/template/.husky/_/husky.sh +27 -0
- package/template/.husky/commit-msg +4 -0
- package/template/.husky/post-commit +4 -0
- package/template/.husky/pre-commit +19 -0
- package/template/.husky/pre-push +12 -0
- package/template/.prettierignore +22 -0
- package/template/.prettierrc +8 -0
- package/template/.sessions/3/2026-03-26T05-41-09-154Z_03bb597f-3898-4a2e-8c08-5746bbe8d95e.jsonl +18 -0
- package/template/.sessions/3/2026-03-26T05-48-30-052Z_12de4774-9cb3-494c-9852-bd1e6b0003c3.jsonl +6 -0
- package/template/.vscode/extensions.json +8 -0
- package/template/.vscode/settings.json +4 -0
- package/template/.workspaces/3/hello.py +1 -0
- package/template/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/803c2e37182b913f8857edf6d8776515cef4af6d48fcb25913e457f4b14ffa97.sqlite +0 -0
- package/template/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/803c2e37182b913f8857edf6d8776515cef4af6d48fcb25913e457f4b14ffa97.sqlite-shm +0 -0
- package/template/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/803c2e37182b913f8857edf6d8776515cef4af6d48fcb25913e457f4b14ffa97.sqlite-wal +0 -0
- package/template/CLAUDE.md +296 -0
- package/template/DESIGN.md +260 -0
- package/template/DEV_CONTEXT_GUIDE.md +366 -0
- package/template/DEV_TOOLS_README.md +396 -0
- package/template/FRAMEWORK_HISTORY.md +34 -0
- package/template/PROJECT_SUMMARY.md +325 -0
- package/template/QUICKSTART.md +305 -0
- package/template/README.md +154 -0
- package/template/STRUCTURE.txt +81 -0
- package/template/admin.html +13 -0
- package/template/auth-inject.html +33 -0
- package/template/commitlint.config.js +12 -0
- package/template/docs/PERMISSION_ARCHITECTURE.md +279 -0
- package/template/docs/PERMISSION_EXAMPLES.md +230 -0
- package/template/docs/PERMISSION_SYSTEM.md +301 -0
- package/template/docs/SMART_TEST.md +171 -0
- package/template/docs/runtime-abstract.md +253 -0
- package/template/drizzle/0000_rainy_boomer.sql +101 -0
- package/template/drizzle/0001_add_todo_attachments.sql +12 -0
- package/template/drizzle/0002_chilly_magneto.sql +89 -0
- package/template/drizzle/meta/0000_snapshot.json +652 -0
- package/template/drizzle/meta/0001_snapshot.json +735 -0
- package/template/drizzle/meta/0002_snapshot.json +1198 -0
- package/template/drizzle/meta/_journal.json +27 -0
- package/template/drizzle.config.ts +13 -0
- package/template/eslint-rules/README-no-middleware-in-routes.md +154 -0
- package/template/eslint-rules/__tests__/enforce-valid-method.test.ts +251 -0
- package/template/eslint-rules/__tests__/layer-boundary.test.ts +102 -0
- package/template/eslint-rules/__tests__/module-boundary.test.ts +77 -0
- package/template/eslint-rules/__tests__/no-direct-zod-import-in-file-routes.test.ts +45 -0
- package/template/eslint-rules/__tests__/no-new-old-service-naming.test.ts +76 -0
- package/template/eslint-rules/e2e-test-location.js +153 -0
- package/template/eslint-rules/enforce-valid-method.js +217 -0
- package/template/eslint-rules/flat-routes-services.js +69 -0
- package/template/eslint-rules/framework-protect.js +186 -0
- package/template/eslint-rules/layer-boundary.js +164 -0
- package/template/eslint-rules/limit-type-complexity.js +130 -0
- package/template/eslint-rules/middleware-location.js +162 -0
- package/template/eslint-rules/module-boundary.js +125 -0
- package/template/eslint-rules/no-ambiguous-file-paths.js +64 -0
- package/template/eslint-rules/no-any-on-apiclient.js +173 -0
- package/template/eslint-rules/no-boolean-success.js +43 -0
- package/template/eslint-rules/no-deep-relative-imports.js +69 -0
- package/template/eslint-rules/no-direct-fetch.js +122 -0
- package/template/eslint-rules/no-direct-ws-sse.js +57 -0
- package/template/eslint-rules/no-direct-zod-import-in-file-routes.js +54 -0
- package/template/eslint-rules/no-disable-direct-fetch.js +57 -0
- package/template/eslint-rules/no-disable-type-safe-client.js +58 -0
- package/template/eslint-rules/no-inline-schema.js +220 -0
- package/template/eslint-rules/no-middleware-in-routes.js +81 -0
- package/template/eslint-rules/no-new-old-service-naming.js +123 -0
- package/template/eslint-rules/no-type-assertion-in-rpc.js +99 -0
- package/template/eslint-rules/no-type-assertion-on-shared-types.js +109 -0
- package/template/eslint-rules/no-util-functions-in-service.js +128 -0
- package/template/eslint-rules/prefer-shared-types.js +352 -0
- package/template/eslint-rules/protect-ws-sse-interface.js +66 -0
- package/template/eslint-rules/require-antd-generic-types.js +73 -0
- package/template/eslint-rules/require-file-openapi-props.js +114 -0
- package/template/eslint-rules/require-hono-chain-syntax.js +129 -0
- package/template/eslint-rules/require-nullable-for-optional.js +99 -0
- package/template/eslint-rules/require-response-helpers.js +140 -0
- package/template/eslint-rules/require-type-safe-test-client.js +106 -0
- package/template/eslint-rules/route-location.js +72 -0
- package/template/eslint.config.js +231 -0
- package/template/index.html +17 -0
- package/template/lint-scripts/DIRECTORY_RULES.md +335 -0
- package/template/lint-scripts/README.md +299 -0
- package/template/lint-scripts/TESTING.md +273 -0
- package/template/lint-scripts/ai-context-generator.ts +159 -0
- package/template/lint-scripts/check-framework-modify.sh +8 -0
- package/template/lint-scripts/check-menu-permissions.js +242 -0
- package/template/lint-scripts/check-refs.ts +165 -0
- package/template/lint-scripts/check-route-auth-complete.js +260 -0
- package/template/lint-scripts/check-route-auth.js +212 -0
- package/template/lint-scripts/check-route-permissions.js +210 -0
- package/template/lint-scripts/config/project.config.ts +358 -0
- package/template/lint-scripts/dev-context-analyzer.ts +707 -0
- package/template/lint-scripts/dev-guide-generator.ts +830 -0
- package/template/lint-scripts/dev-server.ts +58 -0
- package/template/lint-scripts/eslint-route-auth.js +158 -0
- package/template/lint-scripts/eslint-routes-permission.js +147 -0
- package/template/lint-scripts/fix-route-auth.js +245 -0
- package/template/lint-scripts/framework/check-modify.ts +152 -0
- package/template/lint-scripts/framework/hash-utils.ts +180 -0
- package/template/lint-scripts/framework/init-baseline.ts +84 -0
- package/template/lint-scripts/framework/update-baseline.ts +148 -0
- package/template/lint-scripts/post-commit-track.ts +66 -0
- package/template/lint-scripts/quick-test.sh +27 -0
- package/template/lint-scripts/smart-test.ts +268 -0
- package/template/lint-scripts/test-history.ts +360 -0
- package/template/lint-scripts/test-tracker.ts +350 -0
- package/template/lint-scripts/validate-all.ts +250 -0
- package/template/lint-scripts/validators/api-coverage.validator.ts +355 -0
- package/template/lint-scripts/validators/client-rpc.validator.ts +190 -0
- package/template/lint-scripts/validators/client-tests.validator.ts +155 -0
- package/template/lint-scripts/validators/console-log.validator.ts +122 -0
- package/template/lint-scripts/validators/directory-structure.validator.ts +181 -0
- package/template/lint-scripts/validators/imports.validator.ts +180 -0
- package/template/lint-scripts/validators/index.ts +271 -0
- package/template/lint-scripts/validators/md-refs.validator.ts +165 -0
- package/template/lint-scripts/validators/module-tests.validator.ts +112 -0
- package/template/lint-scripts/validators/sensitive.validator.ts +185 -0
- package/template/lint-scripts/validators/server-rpc.validator.ts +141 -0
- package/template/lint-scripts/validators/test-quality.validator.ts +257 -0
- package/template/lint-scripts/validators/todos.validator.ts +126 -0
- package/template/lint-scripts/watch-validator.ts +131 -0
- package/template/modules.config.ts +71 -0
- package/template/package-lock.json +14554 -0
- package/template/package.json +158 -0
- package/template/patches/@hono+zod-openapi+1.2.2.patch +67 -0
- package/template/patches/@hono+zod-validator+0.7.6.patch +13 -0
- package/template/patches/hono+4.12.16.patch +246 -0
- package/template/patches/typescript+5.8.3.patch +26 -0
- package/template/playwright.config.ts +60 -0
- package/template/pnpm-lock.yaml +7137 -0
- package/template/postcss.config.js +6 -0
- package/template/scripts/create-template.ts +1724 -0
- package/template/src/admin/App.tsx +59 -0
- package/template/src/admin/components/AccountSwitcher.tsx +104 -0
- package/template/src/admin/components/CaptchaModal.tsx +150 -0
- package/template/src/admin/components/NotificationDrawer.tsx +213 -0
- package/template/src/admin/components/PageHeader.tsx +45 -0
- package/template/src/admin/components/PermissionConfigEditor.tsx +237 -0
- package/template/src/admin/components/PermissionGuard.tsx +108 -0
- package/template/src/admin/components/PermissionTree.tsx +193 -0
- package/template/src/admin/components/ProtectedRoute.tsx +33 -0
- package/template/src/admin/components/StatsCard.tsx +44 -0
- package/template/src/admin/components/UserFormModal.tsx +67 -0
- package/template/src/admin/components/UserTable.tsx +72 -0
- package/template/src/admin/components/__tests__/PageHeader.test.tsx +48 -0
- package/template/src/admin/components/__tests__/PermissionConfigEditor.test.tsx +147 -0
- package/template/src/admin/components/__tests__/PermissionGuard.test.tsx +221 -0
- package/template/src/admin/components/__tests__/PermissionTree.test.tsx +187 -0
- package/template/src/admin/components/__tests__/ProtectedRoute.test.tsx +129 -0
- package/template/src/admin/components/__tests__/StatsCard.test.tsx +44 -0
- package/template/src/admin/components/__tests__/UserFormModal.test.tsx +125 -0
- package/template/src/admin/components/__tests__/UserTable.test.tsx +89 -0
- package/template/src/admin/components/index.ts +7 -0
- package/template/src/admin/hooks/__tests__/useAuditLogs.test.ts +109 -0
- package/template/src/admin/hooks/__tests__/usePermissions.test.tsx +327 -0
- package/template/src/admin/hooks/__tests__/usePermissionsBranches.test.ts +198 -0
- package/template/src/admin/hooks/__tests__/useRoles.test.ts +261 -0
- package/template/src/admin/hooks/useAdminNotifications.ts +156 -0
- package/template/src/admin/hooks/useAuditLogs.ts +44 -0
- package/template/src/admin/hooks/useConfig.ts +168 -0
- package/template/src/admin/hooks/usePermissions.ts +156 -0
- package/template/src/admin/hooks/useRoles.ts +120 -0
- package/template/src/admin/layouts/Header.tsx +93 -0
- package/template/src/admin/layouts/Layout.tsx +23 -0
- package/template/src/admin/layouts/Sidebar.tsx +120 -0
- package/template/src/admin/main.tsx +18 -0
- package/template/src/admin/pages/ContentPage.tsx +252 -0
- package/template/src/admin/pages/DashboardPage.tsx +141 -0
- package/template/src/admin/pages/DisputesPage.tsx +261 -0
- package/template/src/admin/pages/LoginPage.tsx +121 -0
- package/template/src/admin/pages/MediaTestPage.tsx +524 -0
- package/template/src/admin/pages/OrdersPage.tsx +302 -0
- package/template/src/admin/pages/PermissionsPage.tsx +95 -0
- package/template/src/admin/pages/RegisterPage.tsx +103 -0
- package/template/src/admin/pages/RolesPage.tsx +330 -0
- package/template/src/admin/pages/SettingsPage.tsx +73 -0
- package/template/src/admin/pages/SystemLogsPage.tsx +218 -0
- package/template/src/admin/pages/TestCaptchaPage.tsx +120 -0
- package/template/src/admin/pages/TicketsPage.tsx +292 -0
- package/template/src/admin/pages/UsersPage.tsx +282 -0
- package/template/src/admin/pages/__tests__/ContentPage.test.tsx +213 -0
- package/template/src/admin/pages/__tests__/DashboardPage.test.tsx +153 -0
- package/template/src/admin/pages/__tests__/DisputesPage.test.tsx +196 -0
- package/template/src/admin/pages/__tests__/LoginPage.test.tsx +112 -0
- package/template/src/admin/pages/__tests__/OrdersPage.test.tsx +217 -0
- package/template/src/admin/pages/__tests__/PermissionsPage.test.tsx +119 -0
- package/template/src/admin/pages/__tests__/RegisterPage.test.tsx +179 -0
- package/template/src/admin/pages/__tests__/RolesPage.test.tsx +256 -0
- package/template/src/admin/pages/__tests__/SettingsPage.test.tsx +66 -0
- package/template/src/admin/pages/__tests__/SystemLogsPage.test.tsx +125 -0
- package/template/src/admin/pages/__tests__/TicketsPage.test.tsx +202 -0
- package/template/src/admin/pages/__tests__/UsersPage.test.tsx +229 -0
- package/template/src/admin/services/README.md +263 -0
- package/template/src/admin/services/TESTING.md +323 -0
- package/template/src/admin/services/__tests__/skipLoading.test.ts +146 -0
- package/template/src/admin/services/apiClient.ts +66 -0
- package/template/src/admin/services/requestInterceptor.ts +127 -0
- package/template/src/admin/services/types.ts +5 -0
- package/template/src/admin/stores/__tests__/adminStore.test.ts +184 -0
- package/template/src/admin/stores/__tests__/adminStoreBranches.test.ts +148 -0
- package/template/src/admin/stores/__tests__/captchaStore.test.ts +75 -0
- package/template/src/admin/stores/__tests__/captchaStoreBranches.test.ts +37 -0
- package/template/src/admin/stores/__tests__/loadingStore.test.ts +68 -0
- package/template/src/admin/stores/adminStore.ts +88 -0
- package/template/src/admin/stores/captchaStore.ts +47 -0
- package/template/src/admin/stores/loadingStore.ts +27 -0
- package/template/src/cli/index.ts +22 -0
- package/template/src/cli/modules/config/index.ts +132 -0
- package/template/src/cli/modules/index.ts +12 -0
- package/template/src/cli/modules/notification/index.ts +86 -0
- package/template/src/cli/modules/todo/index.ts +60 -0
- package/template/src/cli/rpc/client.ts +23 -0
- package/template/src/cli/rpc/index.ts +1 -0
- package/template/src/cli/utils/api.ts +17 -0
- package/template/src/cli/utils/auto-command.ts +229 -0
- package/template/src/cli/utils/index.ts +8 -0
- package/template/src/cli/utils/logger.ts +79 -0
- package/template/src/client/App.tsx +24 -0
- package/template/src/client/Layout.tsx +27 -0
- package/template/src/client/components/AuthButton.tsx +41 -0
- package/template/src/client/components/ConnectionStatus.tsx +75 -0
- package/template/src/client/components/EmptyState.tsx +26 -0
- package/template/src/client/components/Footer.tsx +17 -0
- package/template/src/client/components/LoadingSpinner.tsx +26 -0
- package/template/src/client/components/MessageCard.tsx +61 -0
- package/template/src/client/components/Navigation.tsx +65 -0
- package/template/src/client/components/StatusBadge.tsx +50 -0
- package/template/src/client/components/__tests__/App.test.tsx +79 -0
- package/template/src/client/components/__tests__/AuthButton.test.tsx +119 -0
- package/template/src/client/components/__tests__/ConnectionStatus.test.tsx +188 -0
- package/template/src/client/components/__tests__/EmptyState.test.tsx +70 -0
- package/template/src/client/components/__tests__/Footer.test.tsx +22 -0
- package/template/src/client/components/__tests__/LoadingSpinner.test.tsx +75 -0
- package/template/src/client/components/__tests__/MessageCard.test.tsx +142 -0
- package/template/src/client/components/__tests__/Navigation.test.tsx +40 -0
- package/template/src/client/components/__tests__/StatusBadge.test.tsx +107 -0
- package/template/src/client/components/index.ts +6 -0
- package/template/src/client/index.css +27 -0
- package/template/src/client/main.tsx +25 -0
- package/template/src/client/pages/ContentDetailPage.tsx +142 -0
- package/template/src/client/pages/ContentListPage.tsx +169 -0
- package/template/src/client/pages/NotificationPage.tsx +288 -0
- package/template/src/client/pages/TodoPage.tsx +389 -0
- package/template/src/client/pages/WebSocketPage.tsx +239 -0
- package/template/src/client/pages/__tests__/ContentDetailPage.test.tsx +176 -0
- package/template/src/client/pages/__tests__/ContentListPage.test.tsx +202 -0
- package/template/src/client/pages/__tests__/NotificationPage.test.tsx +246 -0
- package/template/src/client/pages/__tests__/TodoPage.test.tsx +433 -0
- package/template/src/client/pages/__tests__/WebSocketPage.test.tsx +378 -0
- package/template/src/client/services/apiClient.ts +66 -0
- package/template/src/client/stores/__tests__/authStore.test.ts +139 -0
- package/template/src/client/stores/__tests__/chatWSStore.test.ts +416 -0
- package/template/src/client/stores/__tests__/notificationStore.test.ts +453 -0
- package/template/src/client/stores/__tests__/todoStore.test.ts +509 -0
- package/template/src/client/stores/authStore.ts +51 -0
- package/template/src/client/stores/chatWSStore.ts +122 -0
- package/template/src/client/stores/notificationStore.ts +199 -0
- package/template/src/client/stores/todoStore.ts +207 -0
- package/template/src/server/__tests__/integration/todos-api.test.ts +138 -0
- package/template/src/server/app.ts +105 -0
- package/template/src/server/config.ts +86 -0
- package/template/src/server/core/__tests__/realtime-core.test.ts +329 -0
- package/template/src/server/core/__tests__/realtime-scanner.test.ts +210 -0
- package/template/src/server/core/__tests__/runtime-node.test.ts +330 -0
- package/template/src/server/core/__tests__/runtime.test.ts +219 -0
- package/template/src/server/core/__tests__/typed-runtime.test.ts +178 -0
- package/template/src/server/core/durable-objects/RealtimeDO.ts +206 -0
- package/template/src/server/core/index.ts +83 -0
- package/template/src/server/core/module-loader.ts +281 -0
- package/template/src/server/core/realtime-core.ts +152 -0
- package/template/src/server/core/realtime-scanner.ts +99 -0
- package/template/src/server/core/runtime-cloudflare.ts +207 -0
- package/template/src/server/core/runtime-node.ts +170 -0
- package/template/src/server/core/runtime.ts +129 -0
- package/template/src/server/core/typed-runtime.ts +80 -0
- package/template/src/server/db/__tests__/driver.test.ts +203 -0
- package/template/src/server/db/config.ts +1 -0
- package/template/src/server/db/driver-cloudflare.ts +50 -0
- package/template/src/server/db/driver.ts +116 -0
- package/template/src/server/db/index.ts +3 -0
- package/template/src/server/db/init.ts +412 -0
- package/template/src/server/db/schema/api-endpoints.ts +23 -0
- package/template/src/server/db/schema/contents.ts +31 -0
- package/template/src/server/db/schema/disputes.ts +39 -0
- package/template/src/server/db/schema/index.ts +14 -0
- package/template/src/server/db/schema/notifications.ts +16 -0
- package/template/src/server/db/schema/orders.ts +30 -0
- package/template/src/server/db/schema/permission-audit-logs.ts +17 -0
- package/template/src/server/db/schema/permission-route-mappings.ts +22 -0
- package/template/src/server/db/schema/permissions.ts +17 -0
- package/template/src/server/db/schema/role-permissions.ts +22 -0
- package/template/src/server/db/schema/roles.ts +17 -0
- package/template/src/server/db/schema/tickets.ts +62 -0
- package/template/src/server/db/schema/todo-attachments.ts +22 -0
- package/template/src/server/db/schema/todos.ts +21 -0
- package/template/src/server/db/schema/user-roles.ts +17 -0
- package/template/src/server/db/seeds/index.ts +1 -0
- package/template/src/server/db/seeds/permission-data.ts +354 -0
- package/template/src/server/db/test-setup.ts +647 -0
- package/template/src/server/entries/cloudflare.ts +71 -0
- package/template/src/server/entries/node.ts +190 -0
- package/template/src/server/index.ts +62 -0
- package/template/src/server/middleware/__tests__/auth-simple.test.ts +147 -0
- package/template/src/server/middleware/__tests__/auth.test.ts +276 -0
- package/template/src/server/middleware/__tests__/captcha.test.ts +258 -0
- package/template/src/server/middleware/__tests__/error-handler.test.ts +364 -0
- package/template/src/server/middleware/__tests__/error-response-format.test.ts +80 -0
- package/template/src/server/middleware/__tests__/permission.test.ts +312 -0
- package/template/src/server/middleware/audit-log.ts +101 -0
- package/template/src/server/middleware/auth.ts +235 -0
- package/template/src/server/middleware/captcha.ts +166 -0
- package/template/src/server/middleware/cors.ts +30 -0
- package/template/src/server/middleware/error-handler.ts +135 -0
- package/template/src/server/middleware/index.ts +24 -0
- package/template/src/server/middleware/logger.ts +73 -0
- package/template/src/server/middleware/permission.ts +127 -0
- package/template/src/server/middleware/rate-limit.ts +69 -0
- package/template/src/server/middleware/realtime-env.ts +12 -0
- package/template/src/server/module-admin/__tests__/admin-routes.test.ts +360 -0
- package/template/src/server/module-admin/__tests__/admin-service.test.ts +222 -0
- package/template/src/server/module-admin/__tests__/audit-log-routes.test.ts +261 -0
- package/template/src/server/module-admin/__tests__/export-routes.test.ts +160 -0
- package/template/src/server/module-admin/__tests__/media-routes.test.ts +122 -0
- package/template/src/server/module-admin/module.ts +40 -0
- package/template/src/server/module-admin/routes/admin-notification-routes.ts +197 -0
- package/template/src/server/module-admin/routes/admin-routes.ts +20 -0
- package/template/src/server/module-admin/routes/auth-routes.ts +86 -0
- package/template/src/server/module-admin/routes/export-routes.ts +207 -0
- package/template/src/server/module-admin/routes/media-routes.ts +78 -0
- package/template/src/server/module-admin/routes/system-routes.ts +89 -0
- package/template/src/server/module-admin/routes/user-management-routes.ts +143 -0
- package/template/src/server/module-admin/services/admin-service.ts +268 -0
- package/template/src/server/module-captcha/__tests__/captcha-route.test.ts +35 -0
- package/template/src/server/module-captcha/__tests__/captcha-service.test.ts +53 -0
- package/template/src/server/module-captcha/module.ts +35 -0
- package/template/src/server/module-captcha/routes/captcha-routes.ts +68 -0
- package/template/src/server/module-captcha/services/captcha-service.ts +3 -0
- package/template/src/server/module-chat/__tests__/chat-routes.test.ts +39 -0
- package/template/src/server/module-chat/__tests__/chat-rpc.test.ts +185 -0
- package/template/src/server/module-chat/__tests__/chat-service.test.ts +64 -0
- package/template/src/server/module-chat/module.ts +27 -0
- package/template/src/server/module-chat/routes/chat-routes.ts +46 -0
- package/template/src/server/module-chat/services/chat-service.ts +38 -0
- package/template/src/server/module-content/__tests__/content-route.test.ts +40 -0
- package/template/src/server/module-content/__tests__/content-service.test.ts +107 -0
- package/template/src/server/module-content/module.ts +39 -0
- package/template/src/server/module-content/routes/content-routes.ts +182 -0
- package/template/src/server/module-content/routes/public-content-routes.ts +58 -0
- package/template/src/server/module-content/services/content-service.ts +224 -0
- package/template/src/server/module-dispute/__tests__/dispute-route.test.ts +43 -0
- package/template/src/server/module-dispute/__tests__/dispute-service.test.ts +135 -0
- package/template/src/server/module-dispute/module.ts +30 -0
- package/template/src/server/module-dispute/routes/dispute-routes.ts +153 -0
- package/template/src/server/module-dispute/services/dispute-service.ts +255 -0
- package/template/src/server/module-file/__tests__/file-routes.test.ts +284 -0
- package/template/src/server/module-file/__tests__/file-storage-service.test.ts +323 -0
- package/template/src/server/module-file/module.ts +28 -0
- package/template/src/server/module-file/routes/file-routes.ts +288 -0
- package/template/src/server/module-notifications/__tests__/notification-route-rpc.test.ts +375 -0
- package/template/src/server/module-notifications/__tests__/notification-service.test.ts +241 -0
- package/template/src/server/module-notifications/__tests__/sse-rpc.test.ts +289 -0
- package/template/src/server/module-notifications/module.ts +32 -0
- package/template/src/server/module-notifications/routes/notification-routes.ts +190 -0
- package/template/src/server/module-notifications/services/notification-service.ts +104 -0
- package/template/src/server/module-order/__tests__/order-route.test.ts +258 -0
- package/template/src/server/module-order/__tests__/order-service.test.ts +222 -0
- package/template/src/server/module-order/module.ts +30 -0
- package/template/src/server/module-order/routes/order-routes.ts +202 -0
- package/template/src/server/module-order/services/order-service.ts +268 -0
- package/template/src/server/module-permission/__tests__/permission-middleware.test.ts +90 -0
- package/template/src/server/module-permission/__tests__/permission-routes.test.ts +330 -0
- package/template/src/server/module-permission/__tests__/permission-service-impl.test.ts +388 -0
- package/template/src/server/module-permission/__tests__/permission-service.test.ts +190 -0
- package/template/src/server/module-permission/__tests__/role-routes.test.ts +215 -0
- package/template/src/server/module-permission/module.ts +68 -0
- package/template/src/server/module-permission/routes/audit-log-routes.ts +76 -0
- package/template/src/server/module-permission/routes/permission-routes.ts +184 -0
- package/template/src/server/module-permission/routes/role-routes.ts +263 -0
- package/template/src/server/module-permission/services/audit-log-service.ts +108 -0
- package/template/src/server/module-permission/services/permission-service-impl.ts +233 -0
- package/template/src/server/module-permission/services/permission-service.ts +256 -0
- package/template/src/server/module-permission/services/role-service.ts +122 -0
- package/template/src/server/module-ticket/__tests__/ticket-route.test.ts +173 -0
- package/template/src/server/module-ticket/__tests__/ticket-service.test.ts +201 -0
- package/template/src/server/module-ticket/module.ts +30 -0
- package/template/src/server/module-ticket/routes/ticket-routes.ts +210 -0
- package/template/src/server/module-ticket/services/ticket-service.ts +297 -0
- package/template/src/server/module-todos/__tests__/todo-service.test.ts +196 -0
- package/template/src/server/module-todos/__tests__/todos-file-upload.test.ts +439 -0
- package/template/src/server/module-todos/__tests__/todos-route-rpc.test.ts +575 -0
- package/template/src/server/module-todos/module.ts +30 -0
- package/template/src/server/module-todos/routes/todos-routes.ts +260 -0
- package/template/src/server/module-todos/services/todo-service.ts +202 -0
- package/template/src/server/route-registry.ts +47 -0
- package/template/src/server/test-utils/test-client.ts +66 -0
- package/template/src/server/test-utils/test-server.ts +131 -0
- package/template/src/server/types/bindings.ts +10 -0
- package/template/src/server/utils/__tests__/app-error.test.ts +374 -0
- package/template/src/server/utils/__tests__/auth.test.ts +76 -0
- package/template/src/server/utils/__tests__/captcha.test.ts +80 -0
- package/template/src/server/utils/__tests__/file-storage.test.ts +472 -0
- package/template/src/server/utils/__tests__/logger.test.ts +221 -0
- package/template/src/server/utils/__tests__/route-helpers.test.ts +264 -0
- package/template/src/server/utils/app-error.ts +355 -0
- package/template/src/server/utils/auth.ts +65 -0
- package/template/src/server/utils/captcha.ts +119 -0
- package/template/src/server/utils/date.ts +50 -0
- package/template/src/server/utils/env.ts +4 -0
- package/template/src/server/utils/file-storage.ts +462 -0
- package/template/src/server/utils/generate.ts +26 -0
- package/template/src/server/utils/id-helpers.ts +5 -0
- package/template/src/server/utils/logger.ts +139 -0
- package/template/src/server/utils/permission-utils.ts +6 -0
- package/template/src/server/utils/response.ts +40 -0
- package/template/src/server/utils/route-helpers.ts +200 -0
- package/template/src/server/utils/uuid.ts +8 -0
- package/template/src/server/utils/ws-helper.ts +30 -0
- package/template/src/shared/constants/index.ts +1 -0
- package/template/src/shared/constants/resource-types.ts +72 -0
- package/template/src/shared/core/__tests__/sse-client.test.ts +376 -0
- package/template/src/shared/core/__tests__/ws-client.test.ts +468 -0
- package/template/src/shared/core/api-request.ts +192 -0
- package/template/src/shared/core/api-schemas.ts +41 -0
- package/template/src/shared/core/index.ts +37 -0
- package/template/src/shared/core/module-manifest.ts +120 -0
- package/template/src/shared/core/protocol-types.ts +26 -0
- package/template/src/shared/core/sse-client.ts +185 -0
- package/template/src/shared/core/ws-client.ts +236 -0
- package/template/src/shared/hooks/index.ts +2 -0
- package/template/src/shared/hooks/useSSE.ts +57 -0
- package/template/src/shared/hooks/useWebSocket.ts +66 -0
- package/template/src/shared/index.ts +5 -0
- package/template/src/shared/modules/admin/index.ts +1 -0
- package/template/src/shared/modules/admin/schemas.ts +100 -0
- package/template/src/shared/modules/audit/index.ts +1 -0
- package/template/src/shared/modules/audit/schemas.ts +24 -0
- package/template/src/shared/modules/captcha/index.ts +1 -0
- package/template/src/shared/modules/captcha/schemas.ts +19 -0
- package/template/src/shared/modules/chat/index.ts +38 -0
- package/template/src/shared/modules/content/index.ts +1 -0
- package/template/src/shared/modules/content/schemas.ts +53 -0
- package/template/src/shared/modules/dispute/index.ts +1 -0
- package/template/src/shared/modules/dispute/schemas.ts +62 -0
- package/template/src/shared/modules/files/index.ts +11 -0
- package/template/src/shared/modules/files/schemas.ts +44 -0
- package/template/src/shared/modules/index.ts +70 -0
- package/template/src/shared/modules/notifications/index.ts +26 -0
- package/template/src/shared/modules/notifications/schemas.ts +73 -0
- package/template/src/shared/modules/order/index.ts +1 -0
- package/template/src/shared/modules/order/schemas.ts +63 -0
- package/template/src/shared/modules/permission/index.ts +42 -0
- package/template/src/shared/modules/permission/permission-dependencies.ts +50 -0
- package/template/src/shared/modules/permission/permissions.ts +275 -0
- package/template/src/shared/modules/permission/schemas.ts +92 -0
- package/template/src/shared/modules/permission/types.ts +23 -0
- package/template/src/shared/modules/role/index.ts +1 -0
- package/template/src/shared/modules/role/schemas.ts +46 -0
- package/template/src/shared/modules/ticket/index.ts +1 -0
- package/template/src/shared/modules/ticket/schemas.ts +77 -0
- package/template/src/shared/modules/todos/index.ts +20 -0
- package/template/src/shared/modules/todos/schemas.ts +65 -0
- package/template/src/shared/schemas/index.ts +79 -0
- package/template/src/types/global.d.ts +14 -0
- package/template/src/vite-env.d.ts +21 -0
- package/template/tailwind.config.js +11 -0
- package/template/tests/e2e/admin-crud.spec.ts +81 -0
- package/template/tests/e2e/case-o1.spec.ts +108 -0
- package/template/tests/e2e/global-setup.ts +73 -0
- package/template/tests/e2e/global-teardown.ts +28 -0
- package/template/tests/e2e/notification.spec.ts +458 -0
- package/template/tests/e2e/todo.spec.ts +623 -0
- package/template/tests/e2e/websocket.spec.ts +254 -0
- package/template/tsconfig.json +35 -0
- package/template/tsup.config.ts +95 -0
- package/template/vite-plugins.ts +113 -0
- package/template/vite.config.ts +96 -0
- package/template/vitest.config.ts +53 -0
- package/template/vitest.integration.config.ts +24 -0
- package/template/vitest.integration.setup.ts +22 -0
- package/template/vitest.setup.ts +74 -0
- 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
|
+
*/
|