@etus/bhono-app 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +13 -0
- package/dist/cli.js +46 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +26 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/generator.d.ts +14 -0
- package/dist/generator.js +142 -0
- package/dist/generator.js.map +1 -0
- package/dist/generator.test.d.ts +1 -0
- package/dist/generator.test.js +127 -0
- package/dist/generator.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +97 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts.d.ts +25 -0
- package/dist/prompts.js +83 -0
- package/dist/prompts.js.map +1 -0
- package/dist/prompts.test.d.ts +1 -0
- package/dist/prompts.test.js +24 -0
- package/dist/prompts.test.js.map +1 -0
- package/dist/providers/cloudflare.d.ts +37 -0
- package/dist/providers/cloudflare.js +61 -0
- package/dist/providers/cloudflare.js.map +1 -0
- package/dist/providers/cloudflare.test.d.ts +1 -0
- package/dist/providers/cloudflare.test.js +29 -0
- package/dist/providers/cloudflare.test.js.map +1 -0
- package/dist/providers/github.d.ts +16 -0
- package/dist/providers/github.js +57 -0
- package/dist/providers/github.js.map +1 -0
- package/dist/providers/github.test.d.ts +1 -0
- package/dist/providers/github.test.js +16 -0
- package/dist/providers/github.test.js.map +1 -0
- package/dist/templates.d.ts +8 -0
- package/dist/templates.js +88 -0
- package/dist/templates.js.map +1 -0
- package/dist/templates.test.d.ts +1 -0
- package/dist/templates.test.js +26 -0
- package/dist/templates.test.js.map +1 -0
- package/package.json +36 -0
- package/templates/base/.claude/agents/architect-review.md +160 -0
- package/templates/base/.claude/agents/backend-architect.md +308 -0
- package/templates/base/.claude/agents/code-reviewer.md +170 -0
- package/templates/base/.claude/agents/performance-engineer.md +166 -0
- package/templates/base/.claude/agents/test-automator.md +219 -0
- package/templates/base/.claude/commands/check-skill-rules.md +53 -0
- package/templates/base/.claude/commands/claude-md.md +250 -0
- package/templates/base/.claude/commands/code-prompt.md +212 -0
- package/templates/base/.claude/commands/explain-code.md +194 -0
- package/templates/base/.claude/commands/init-projec.md +89 -0
- package/templates/base/.claude/commands/linear/README.md +297 -0
- package/templates/base/.claude/commands/linear/create-issue.md +190 -0
- package/templates/base/.claude/commands/linear/implement-issue.md +248 -0
- package/templates/base/.claude/commands/linear/process-triage.md +399 -0
- package/templates/base/.claude/commands/linear/setup.md +180 -0
- package/templates/base/.claude/commands/prime.md +9 -0
- package/templates/base/.claude/commands/review-gap.md +10 -0
- package/templates/base/.claude/commands/setup-aa.md +311 -0
- package/templates/base/.claude/commands/ship.md +262 -0
- package/templates/base/.claude/commands/tools.md +3 -0
- package/templates/base/.claude/docs/claude-progress.txt +107 -0
- package/templates/base/.claude/hooks/package-lock.json +556 -0
- package/templates/base/.claude/hooks/package.json +16 -0
- package/templates/base/.claude/hooks/skill-activation-prompt.sh +7 -0
- package/templates/base/.claude/hooks/skill-activation-prompt.ts +142 -0
- package/templates/base/.claude/hooks/tsconfig.json +19 -0
- package/templates/base/.claude/scripts/check-updates.sh +85 -0
- package/templates/base/.claude/scripts/install_pkgs.sh +66 -0
- package/templates/base/.claude/scripts/setup-project.sh +177 -0
- package/templates/base/.claude/scripts/validate-skill-rules.sh +94 -0
- package/templates/base/.claude/settings.json +113 -0
- package/templates/base/.claude/settings.local.json +11 -0
- package/templates/base/.claude/skills/architecture-analyzer/SKILL.md +531 -0
- package/templates/base/.claude/skills/architecture-analyzer/assets/report-template.md +215 -0
- package/templates/base/.claude/skills/architecture-analyzer/references/c4-templates.md +234 -0
- package/templates/base/.claude/skills/architecture-analyzer/references/confidence-levels.md +203 -0
- package/templates/base/.claude/skills/architecture-analyzer/scripts/analyze_structure.py +266 -0
- package/templates/base/.claude/skills/architecture-analyzer/scripts/analyze_tech_debt.py +776 -0
- package/templates/base/.claude/skills/architecture-analyzer/scripts/extract_apis.py +338 -0
- package/templates/base/.claude/skills/architecture-analyzer/scripts/generate_c4.py +283 -0
- package/templates/base/.claude/skills/architecture-analyzer/scripts/generate_erd.py +935 -0
- package/templates/base/.claude/skills/architecture-analyzer/scripts/map_dependencies.py +555 -0
- package/templates/base/.claude/skills/dev-browser/SKILL.md +318 -0
- package/templates/base/.claude/skills/dev-browser/bun.lock +443 -0
- package/templates/base/.claude/skills/dev-browser/package-lock.json +2927 -0
- package/templates/base/.claude/skills/dev-browser/package.json +27 -0
- package/templates/base/.claude/skills/dev-browser/scripts/start-server.ts +117 -0
- package/templates/base/.claude/skills/dev-browser/server.sh +24 -0
- package/templates/base/.claude/skills/dev-browser/src/client.ts +403 -0
- package/templates/base/.claude/skills/dev-browser/src/index.ts +281 -0
- package/templates/base/.claude/skills/dev-browser/src/snapshot/__tests__/snapshot.test.ts +223 -0
- package/templates/base/.claude/skills/dev-browser/src/snapshot/browser-script.ts +877 -0
- package/templates/base/.claude/skills/dev-browser/src/snapshot/index.ts +14 -0
- package/templates/base/.claude/skills/dev-browser/src/snapshot/inject.ts +13 -0
- package/templates/base/.claude/skills/dev-browser/src/types.ts +27 -0
- package/templates/base/.claude/skills/dev-browser/tsconfig.json +36 -0
- package/templates/base/.claude/skills/dev-browser/vitest.config.ts +12 -0
- package/templates/base/.claude/skills/linear/SKILL.md +440 -0
- package/templates/base/.claude/skills/linear/examples.md +262 -0
- package/templates/base/.claude/skills/linear/lib/client.ts +51 -0
- package/templates/base/.claude/skills/linear/lib/config.ts +106 -0
- package/templates/base/.claude/skills/linear/lib/output.ts +34 -0
- package/templates/base/.claude/skills/linear/package-lock.json +698 -0
- package/templates/base/.claude/skills/linear/package.json +27 -0
- package/templates/base/.claude/skills/linear/reference.md +263 -0
- package/templates/base/.claude/skills/linear/scripts/comments/create.ts +47 -0
- package/templates/base/.claude/skills/linear/scripts/comments/list.ts +47 -0
- package/templates/base/.claude/skills/linear/scripts/issues/archive.ts +30 -0
- package/templates/base/.claude/skills/linear/scripts/issues/create.ts +279 -0
- package/templates/base/.claude/skills/linear/scripts/issues/get.ts +68 -0
- package/templates/base/.claude/skills/linear/scripts/issues/list.ts +67 -0
- package/templates/base/.claude/skills/linear/scripts/issues/update.ts +281 -0
- package/templates/base/.claude/skills/linear/scripts/labels/add-to-issue.ts +63 -0
- package/templates/base/.claude/skills/linear/scripts/labels/create.ts +45 -0
- package/templates/base/.claude/skills/linear/scripts/labels/list.ts +30 -0
- package/templates/base/.claude/skills/linear/scripts/list-teams.ts +52 -0
- package/templates/base/.claude/skills/linear/scripts/setup/setup-credentials.ts +96 -0
- package/templates/base/.claude/skills/linear/scripts/status/list.ts +31 -0
- package/templates/base/.claude/skills/linear/scripts/status/set-by-name.ts +78 -0
- package/templates/base/.claude/skills/linear/scripts/status/update.ts +44 -0
- package/templates/base/.claude/skills/linear/scripts/users/list.ts +59 -0
- package/templates/base/.claude/skills/linear/scripts/users/me.ts +20 -0
- package/templates/base/.claude/skills/linear/templates/README.md +203 -0
- package/templates/base/.claude/skills/linear/templates/api-reference.md +258 -0
- package/templates/base/.claude/skills/linear/templates/bug-report.md +99 -0
- package/templates/base/.claude/skills/linear/templates/feature-request.md +118 -0
- package/templates/base/.claude/skills/linear/templates/security-issue.md +162 -0
- package/templates/base/.claude/skills/linear/templates/sprint-task.md +175 -0
- package/templates/base/.claude/skills/linear/templates/tech-debt.md +137 -0
- package/templates/base/.claude/skills/linear/tsconfig.json +17 -0
- package/templates/base/.claude/skills/linear/workflows/issue-lifecycle.md +317 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/SKILL.md +113 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/assets/global-setup.template.js +97 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/assets/playwright.config.template.js +171 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/assets/test-template.spec.js +163 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/examples/README.md +26 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/examples/ads.email-deeplink.spec.ts +12 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/examples/mobile.realism.spec.ts +16 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/examples/smoke.home.spec.ts +6 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/architecture.md +578 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/best-practices.md +260 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/ci-reporting.md +86 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/debugging.md +629 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/mobile-realism.md +50 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/optimization.md +488 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/patterns.md +513 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/resources.md +44 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/references/visual-a11y.md +66 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/scripts/auth-setup.js +202 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/scripts/performance-analyzer.js +240 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/scripts/trace-url.js +132 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/ci/github-actions.playwright.yml +78 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/fixtures.ts +44 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/global-setup.template.js +97 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/global.setup.ts +35 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/helpers/ad-gpt-observer.ts +80 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/helpers/chromium-mobile-profile.ts +93 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/playwright.config.template.js +171 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/playwright.config.ts +126 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/test-template.spec.js +163 -0
- package/templates/base/.claude/skills/playwright-e2e-testing/templates/tests/email-deeplink.ads.spec.ts +44 -0
- package/templates/base/.claude/skills/skill-rules.json +184 -0
- package/templates/base/.claude/skills/wrangler/SKILL.md +209 -0
- package/templates/base/.claude/skills/wrangler/resources/api.md +494 -0
- package/templates/base/.claude/skills/wrangler/resources/bundling.md +83 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/cert.md +64 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/check.md +66 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/containers.md +157 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/d1.md +843 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/delete.md +27 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/deploy.md +139 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/deployments.md +56 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/dev.md +157 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/dispatch-namespace.md +69 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/docs.md +61 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/how-to-run.md +62 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/hyperdrive.md +425 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/init.md +31 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/kv-bulk.md +265 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/kv-key.md +353 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/kv-namespace.md +265 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/login.md +23 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/logout.md +19 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/mtls-certificate.md +69 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/pages.md +175 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/pipelines.md +76 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/queues.md +132 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/r2-bucket.md +342 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/r2-object.md +267 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/r2-sql.md +65 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/rollback.md +40 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/secret.md +308 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/secrets-store-secret.md +100 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/secrets-store-store.md +60 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/setup.md +67 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/tail.md +37 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/telemetry.md +64 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/triggers.md +39 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/types.md +73 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/vectorize.md +941 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/versions.md +95 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/whoami.md +49 -0
- package/templates/base/.claude/skills/wrangler/resources/commands/workflows.md +117 -0
- package/templates/base/.claude/skills/wrangler/resources/commands.md +138 -0
- package/templates/base/.claude/skills/wrangler/resources/configuration.md +2176 -0
- package/templates/base/.claude/skills/wrangler/resources/custom-builds.md +55 -0
- package/templates/base/.claude/skills/wrangler/resources/deprecations.md +279 -0
- package/templates/base/.claude/skills/wrangler/resources/enviroments.md +416 -0
- package/templates/base/.claude/skills/wrangler/resources/extract_sections.py +119 -0
- package/templates/base/.claude/skills/wrangler/resources/process_content.py +94 -0
- package/templates/base/.claude/skills/wrangler/resources/system-enviroments-variables.md +120 -0
- package/templates/base/.dev.vars.example +15 -0
- package/templates/base/.env.example +29 -0
- package/templates/base/.github/workflows/test.yml +127 -0
- package/templates/base/.nycrc.json +16 -0
- package/templates/base/CLAUDE.md +218 -0
- package/templates/base/README.md +670 -0
- package/templates/base/auth-setup-error.png +0 -0
- package/templates/base/config/drizzle.config.ts +10 -0
- package/templates/base/config/eslint.config.js +364 -0
- package/templates/base/config/wrangler.json +76 -0
- package/templates/base/docs/app_spec.txt +879 -0
- package/templates/base/docs/app_spec_template.md +681 -0
- package/templates/base/docs/architecture/README.md +8 -0
- package/templates/base/docs/architecture/data-requirements.md +109 -0
- package/templates/base/docs/architecture/erd.md +91 -0
- package/templates/base/docs/features/feature_list.json +3128 -0
- package/templates/base/docs/hono-boilerplate-plan.md +1774 -0
- package/templates/base/docs/test-coverage-gap-analysis.md +242 -0
- package/templates/base/docs/testing.md +188 -0
- package/templates/base/index.html +16 -0
- package/templates/base/package.json +115 -0
- package/templates/base/playwright.config.ts +158 -0
- package/templates/base/pnpm-lock.yaml +8175 -0
- package/templates/base/scripts/capture-prod-session.ts +250 -0
- package/templates/base/scripts/generate-openapi.ts +23 -0
- package/templates/base/scripts/init.sh +121 -0
- package/templates/base/src/client/__tests__/button.test.tsx +30 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-dashboard-stats-cards-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-quick-action-cards-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-recent-activity-section-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-user-first-name-in-welcome-message-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-user-information-in-sidebar-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-render-dashboard-when-authenticated-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-show-navigation-sidebar-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-unauthenticated-should-redirect-to-login-when-not-authenticated-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-display-Google-OAuth-login-button-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-have-a-link-back-to-home-page-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-content-without-waiting-for-authentication-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-page-at--login-route-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-show-Terms-of-Service-and-Privacy-Policy-links-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-trigger-OAuth-flow-when-clicking-login-button-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-display-404-text-on-not-found-page-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-have-navigation-options-on-404-page-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-render-404-page-for-unknown-routes-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-account-page-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-integrations-page-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-settings-page-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-team-page-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-home-page-navigation-should-display-navigation-links-on-home-page-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-home-page-navigation-should-have-correct-link-destinations-on-home-page-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-allow-access-to-home-page-without-authentication-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-allow-access-to-login-page-without-authentication-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-dashboard-to-login-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-settings-to-login-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-team-to-login-1.png +0 -0
- package/templates/base/src/client/__tests__/routes/authenticated-layout.test.tsx +252 -0
- package/templates/base/src/client/__tests__/routes/dashboard.test.tsx +136 -0
- package/templates/base/src/client/__tests__/routes/error-components.test.tsx +186 -0
- package/templates/base/src/client/__tests__/routes/login.test.tsx +112 -0
- package/templates/base/src/client/__tests__/routes/navigation.test.tsx +272 -0
- package/templates/base/src/client/__tests__/routes/root-layout.test.tsx +65 -0
- package/templates/base/src/client/__tests__/setup-browser.ts +15 -0
- package/templates/base/src/client/__tests__/setup.ts +32 -0
- package/templates/base/src/client/__tests__/test-utils.tsx +135 -0
- package/templates/base/src/client/api/.gitkeep +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-can-be-collapsed-by-default-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-expands-when-collapsed-and-expand-button-is-clicked-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-handles-logout-button-click-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-handles-navigation-clicks-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-hides-navigation-labels-when-collapsed-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-highlights-active-route-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-renders-sidebar-navigation-items-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-keyboard-shortcut-hint-when-expanded-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-info-when-authenticated-1.png +0 -0
- package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-initials-in-avatar-fallback-1.png +0 -0
- package/templates/base/src/client/components/__tests__/error-boundary.test.tsx +97 -0
- package/templates/base/src/client/components/__tests__/sidebar.test.tsx +281 -0
- package/templates/base/src/client/components/error-boundary.tsx +68 -0
- package/templates/base/src/client/components/icons.tsx +106 -0
- package/templates/base/src/client/components/layout/.gitkeep +0 -0
- package/templates/base/src/client/components/sidebar.tsx +267 -0
- package/templates/base/src/client/components/ui/.gitkeep +0 -0
- package/templates/base/src/client/components/ui/__tests__/avatar.test.tsx +308 -0
- package/templates/base/src/client/components/ui/__tests__/card.test.tsx +214 -0
- package/templates/base/src/client/components/ui/__tests__/dialog.test.tsx +297 -0
- package/templates/base/src/client/components/ui/__tests__/error-fallback.test.tsx +145 -0
- package/templates/base/src/client/components/ui/__tests__/input.test.tsx +98 -0
- package/templates/base/src/client/components/ui/__tests__/loading-skeleton.test.tsx +139 -0
- package/templates/base/src/client/components/ui/__tests__/skeleton.test.tsx +44 -0
- package/templates/base/src/client/components/ui/__tests__/sonner.test.tsx +28 -0
- package/templates/base/src/client/components/ui/__tests__/tabs.test.tsx +233 -0
- package/templates/base/src/client/components/ui/avatar.tsx +101 -0
- package/templates/base/src/client/components/ui/badge.tsx +46 -0
- package/templates/base/src/client/components/ui/button.tsx +72 -0
- package/templates/base/src/client/components/ui/card.tsx +86 -0
- package/templates/base/src/client/components/ui/dialog.tsx +140 -0
- package/templates/base/src/client/components/ui/error-fallback.tsx +179 -0
- package/templates/base/src/client/components/ui/form.tsx +172 -0
- package/templates/base/src/client/components/ui/input.tsx +24 -0
- package/templates/base/src/client/components/ui/label.tsx +22 -0
- package/templates/base/src/client/components/ui/loading-skeleton.tsx +154 -0
- package/templates/base/src/client/components/ui/separator.tsx +33 -0
- package/templates/base/src/client/components/ui/skeleton.tsx +16 -0
- package/templates/base/src/client/components/ui/sonner.tsx +29 -0
- package/templates/base/src/client/components/ui/tabs.tsx +121 -0
- package/templates/base/src/client/hooks/.gitkeep +0 -0
- package/templates/base/src/client/hooks/__tests__/use-auth.test.tsx +306 -0
- package/templates/base/src/client/hooks/__tests__/use-theme.test.tsx +172 -0
- package/templates/base/src/client/hooks/use-auth.ts +53 -0
- package/templates/base/src/client/hooks/use-theme.tsx +78 -0
- package/templates/base/src/client/index.css +881 -0
- package/templates/base/src/client/lib/query-client.ts +11 -0
- package/templates/base/src/client/lib/utils.ts +7 -0
- package/templates/base/src/client/main.tsx +26 -0
- package/templates/base/src/client/routeTree.gen.ts +258 -0
- package/templates/base/src/client/router.ts +15 -0
- package/templates/base/src/client/routes/$.tsx +77 -0
- package/templates/base/src/client/routes/.gitkeep +0 -0
- package/templates/base/src/client/routes/__root.tsx +34 -0
- package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-accept-invitation-button-1.png +0 -0
- package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-decline-button-linking-to-homepage-1.png +0 -0
- package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-invitation-details--email--workspace--role--1.png +0 -0
- package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-have-a-logo-link-to-homepage-1.png +0 -0
- package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-render-invitation-page-with-inviter-name-and-workspace-1.png +0 -0
- package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-show-terms-of-service-and-privacy-policy-links-1.png +0 -0
- package/templates/base/src/client/routes/__tests__/invite.test.tsx +138 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-render-Active-Sessions-section-with-session-cards-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-render-page-with-correct-title--Account--1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-API-Access-section-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-Connected-Accounts-section-with-Google-connected-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-Danger-Zone-section-with-delete-button-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-Security-section-with-Two-Factor-Authentication-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-API-documentation-section-should-display-API-documentation-link-section-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-Create-Webhook-Dialog-should-have-Add-Webhook-trigger-button-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-category-filters-should-display-all-category-buttons-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-display-all-integration-cards-with-names-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-display-category-badges-on-cards-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Configure-button-for-connected-integrations-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Connect-button-for-not-connected-integrations-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Connected-badge-for-connected-integrations-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Available-Integrations-section-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Webhooks-section-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-connected-count-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-page-description-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-search-input-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-render-with-correct-title--Integrations--1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-filter-integrations-based-on-search-query-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-search-by-description-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-show-no-results-message-when-search-has-no-matches-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-existing-webhook-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-events-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-last-delivery-info-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-success-status-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-have-Add-Webhook-button-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Account-tab-should-display-connected-accounts-section-with-Google-provider-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Account-tab-should-display-sessions-and-danger-zone-sections-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-all-notification-toggle-options-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-email-notifications-section-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-toggle-switches-for-notification-options-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-have-toggles-checked-by-default-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display--Save-Changes--button-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-personal-information-form-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-profile-picture-section-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-email-in-disabled-email-input-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-initials-in-avatar-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-name-in-the-name-input-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-all-three-tabs-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-page-description-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-render-with-correct-title--Settings--1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-show-Profile-tab-as-default-active-tab-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-switch-to-Account-tab-when-clicked-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-switch-to-Notifications-tab-when-clicked-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-display-Active-Members-section-with-member-count-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-display-Pending-Invitations-section-with-invitation-details-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-have-invite-member-button-that-can-be-clicked-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-render-page-with-correct-title-and-description-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-render-search-input-that-filters-team-members-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-show-current-user-with---you---indicator-and-role-badge-1.png +0 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/account.test.tsx +324 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/integrations.test.tsx +520 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/settings.test.tsx +414 -0
- package/templates/base/src/client/routes/_authenticated/__tests__/team.test.tsx +374 -0
- package/templates/base/src/client/routes/_authenticated/account.tsx +416 -0
- package/templates/base/src/client/routes/_authenticated/dashboard.tsx +151 -0
- package/templates/base/src/client/routes/_authenticated/integrations.tsx +553 -0
- package/templates/base/src/client/routes/_authenticated/settings.tsx +310 -0
- package/templates/base/src/client/routes/_authenticated/team.tsx +395 -0
- package/templates/base/src/client/routes/_authenticated.tsx +69 -0
- package/templates/base/src/client/routes/index.tsx +155 -0
- package/templates/base/src/client/routes/invite.$token.tsx +191 -0
- package/templates/base/src/client/routes/login.tsx +92 -0
- package/templates/base/src/server/__tests__/fixtures.ts +461 -0
- package/templates/base/src/server/__tests__/mocks/__tests__/db.test.ts +239 -0
- package/templates/base/src/server/__tests__/mocks/__tests__/kv.test.ts +293 -0
- package/templates/base/src/server/__tests__/mocks/__tests__/r2.test.ts +363 -0
- package/templates/base/src/server/__tests__/mocks/db.ts +186 -0
- package/templates/base/src/server/__tests__/mocks/index.ts +33 -0
- package/templates/base/src/server/__tests__/mocks/kv.ts +286 -0
- package/templates/base/src/server/__tests__/mocks/r2.ts +397 -0
- package/templates/base/src/server/__tests__/setup.ts +281 -0
- package/templates/base/src/server/auth/__tests__/guards.test.ts +162 -0
- package/templates/base/src/server/auth/guards.ts +92 -0
- package/templates/base/src/server/auth/permissions.test.ts +45 -0
- package/templates/base/src/server/auth/permissions.ts +139 -0
- package/templates/base/src/server/auth/roles.test.ts +169 -0
- package/templates/base/src/server/auth/roles.ts +141 -0
- package/templates/base/src/server/db/client.ts +12 -0
- package/templates/base/src/server/db/schema/accounts.ts +20 -0
- package/templates/base/src/server/db/schema/audit-logs.ts +26 -0
- package/templates/base/src/server/db/schema/index.ts +7 -0
- package/templates/base/src/server/db/schema/invitations.ts +30 -0
- package/templates/base/src/server/db/schema/refresh-tokens.ts +22 -0
- package/templates/base/src/server/db/schema/user-accounts.ts +25 -0
- package/templates/base/src/server/db/schema/users.ts +33 -0
- package/templates/base/src/server/db/seed.ts +267 -0
- package/templates/base/src/server/env.test.ts +84 -0
- package/templates/base/src/server/env.ts +78 -0
- package/templates/base/src/server/index.ts +82 -0
- package/templates/base/src/server/lib/audit.ts +73 -0
- package/templates/base/src/server/lib/audited-db.test.ts +107 -0
- package/templates/base/src/server/lib/audited-db.ts +154 -0
- package/templates/base/src/server/lib/email.test.ts +116 -0
- package/templates/base/src/server/lib/email.ts +82 -0
- package/templates/base/src/server/lib/errors.test.ts +49 -0
- package/templates/base/src/server/lib/errors.ts +64 -0
- package/templates/base/src/server/lib/oauth.test.ts +238 -0
- package/templates/base/src/server/lib/oauth.ts +113 -0
- package/templates/base/src/server/lib/pagination.test.ts +52 -0
- package/templates/base/src/server/lib/pagination.ts +32 -0
- package/templates/base/src/server/lib/password.test.ts +151 -0
- package/templates/base/src/server/lib/password.ts +151 -0
- package/templates/base/src/server/lib/providers.test.ts +105 -0
- package/templates/base/src/server/lib/providers.ts +62 -0
- package/templates/base/src/server/lib/r2-storage.test.ts +202 -0
- package/templates/base/src/server/lib/r2-storage.ts +107 -0
- package/templates/base/src/server/lib/schema-helpers.ts +16 -0
- package/templates/base/src/server/lib/session.test.ts +758 -0
- package/templates/base/src/server/lib/session.ts +267 -0
- package/templates/base/src/server/lib/tokens.test.ts +208 -0
- package/templates/base/src/server/lib/tokens.ts +65 -0
- package/templates/base/src/server/lib/transaction.test.ts +45 -0
- package/templates/base/src/server/lib/transaction.ts +24 -0
- package/templates/base/src/server/middleware/account.test.ts +201 -0
- package/templates/base/src/server/middleware/account.ts +66 -0
- package/templates/base/src/server/middleware/auth.test.ts +345 -0
- package/templates/base/src/server/middleware/auth.ts +146 -0
- package/templates/base/src/server/middleware/cors.test.ts +88 -0
- package/templates/base/src/server/middleware/cors.ts +26 -0
- package/templates/base/src/server/middleware/error-handler.test.ts +69 -0
- package/templates/base/src/server/middleware/error-handler.ts +43 -0
- package/templates/base/src/server/middleware/index.ts +8 -0
- package/templates/base/src/server/middleware/rate-limit.test.ts +472 -0
- package/templates/base/src/server/middleware/rate-limit.ts +294 -0
- package/templates/base/src/server/middleware/request-context.test.ts +175 -0
- package/templates/base/src/server/middleware/request-context.ts +28 -0
- package/templates/base/src/server/middleware/request-logger.test.ts +92 -0
- package/templates/base/src/server/middleware/request-logger.ts +50 -0
- package/templates/base/src/server/routes/accounts/__tests__/handlers.test.ts +460 -0
- package/templates/base/src/server/routes/accounts/handlers.ts +179 -0
- package/templates/base/src/server/routes/accounts/index.ts +55 -0
- package/templates/base/src/server/routes/accounts/routes.ts +205 -0
- package/templates/base/src/server/routes/accounts/schemas.ts +31 -0
- package/templates/base/src/server/routes/api.ts +37 -0
- package/templates/base/src/server/routes/audits/__tests__/handlers.test.ts +349 -0
- package/templates/base/src/server/routes/audits/handlers.ts +37 -0
- package/templates/base/src/server/routes/audits/index.ts +14 -0
- package/templates/base/src/server/routes/audits/routes.ts +29 -0
- package/templates/base/src/server/routes/audits/schemas.ts +43 -0
- package/templates/base/src/server/routes/auth/__tests__/handlers.test.ts +381 -0
- package/templates/base/src/server/routes/auth/handlers.ts +222 -0
- package/templates/base/src/server/routes/auth/index.ts +37 -0
- package/templates/base/src/server/routes/auth/routes.ts +136 -0
- package/templates/base/src/server/routes/auth/schemas.ts +48 -0
- package/templates/base/src/server/routes/auth/test-login.ts +156 -0
- package/templates/base/src/server/routes/health/__tests__/handlers.test.ts +237 -0
- package/templates/base/src/server/routes/health/handlers.ts +83 -0
- package/templates/base/src/server/routes/health/index.ts +13 -0
- package/templates/base/src/server/routes/health/routes.ts +90 -0
- package/templates/base/src/server/routes/index.ts +53 -0
- package/templates/base/src/server/routes/invitations/__tests__/handlers.test.ts +473 -0
- package/templates/base/src/server/routes/invitations/handlers.ts +71 -0
- package/templates/base/src/server/routes/invitations/index.ts +25 -0
- package/templates/base/src/server/routes/invitations/routes.ts +69 -0
- package/templates/base/src/server/routes/invitations/schemas.ts +39 -0
- package/templates/base/src/server/routes/openapi.ts +14 -0
- package/templates/base/src/server/routes/schemas.ts +53 -0
- package/templates/base/src/server/routes/storage/__tests__/handlers.test.ts +408 -0
- package/templates/base/src/server/routes/storage/handlers.ts +100 -0
- package/templates/base/src/server/routes/storage/index.ts +42 -0
- package/templates/base/src/server/routes/storage/routes.ts +91 -0
- package/templates/base/src/server/routes/storage/schemas.ts +56 -0
- package/templates/base/src/server/routes/users/__tests__/handlers.test.ts +526 -0
- package/templates/base/src/server/routes/users/handlers.ts +228 -0
- package/templates/base/src/server/routes/users/index.ts +67 -0
- package/templates/base/src/server/routes/users/routes.ts +265 -0
- package/templates/base/src/server/routes/users/schemas.ts +67 -0
- package/templates/base/src/server/services/__tests__/accounts.test.ts +764 -0
- package/templates/base/src/server/services/__tests__/audits.test.ts +235 -0
- package/templates/base/src/server/services/__tests__/auth.test.ts +765 -0
- package/templates/base/src/server/services/__tests__/invitations.test.ts +704 -0
- package/templates/base/src/server/services/__tests__/users.test.ts +755 -0
- package/templates/base/src/server/services/accounts.ts +269 -0
- package/templates/base/src/server/services/audits.ts +82 -0
- package/templates/base/src/server/services/auth.ts +225 -0
- package/templates/base/src/server/services/index.ts +6 -0
- package/templates/base/src/server/services/invitations.ts +306 -0
- package/templates/base/src/server/services/users.ts +350 -0
- package/templates/base/src/server/types/auth.ts +36 -0
- package/templates/base/src/server/types/index.ts +117 -0
- package/templates/base/src/shared/schemas/.gitkeep +0 -0
- package/templates/base/src/shared/schemas/__tests__/schemas.test.ts +547 -0
- package/templates/base/src/shared/schemas/account.ts +15 -0
- package/templates/base/src/shared/schemas/index.ts +6 -0
- package/templates/base/src/shared/schemas/invitation.ts +9 -0
- package/templates/base/src/shared/schemas/profile.ts +10 -0
- package/templates/base/src/shared/schemas/user.ts +16 -0
- package/templates/base/src/shared/schemas/webhook.ts +12 -0
- package/templates/base/src/shared/types/.gitkeep +0 -0
- package/templates/base/src/shared/types/account.ts +12 -0
- package/templates/base/src/shared/types/api.ts +1399 -0
- package/templates/base/src/shared/types/auth.ts +24 -0
- package/templates/base/src/shared/types/index.ts +5 -0
- package/templates/base/src/shared/types/user.ts +31 -0
- package/templates/base/src/test/vitest-zod-matcher.ts +37 -0
- package/templates/base/src/test/vitest.d.ts +19 -0
- package/templates/base/tests/e2e/README.md +141 -0
- package/templates/base/tests/e2e/a11y/accessibility.spec.ts +925 -0
- package/templates/base/tests/e2e/a11y/keyboard-navigation.spec.ts +610 -0
- package/templates/base/tests/e2e/api/accounts.spec.ts +148 -0
- package/templates/base/tests/e2e/api/audit-logs.spec.ts +130 -0
- package/templates/base/tests/e2e/api/authenticated-api.spec.ts +311 -0
- package/templates/base/tests/e2e/api/storage.spec.ts +109 -0
- package/templates/base/tests/e2e/auth-flows.unauth.spec.ts +117 -0
- package/templates/base/tests/e2e/auth-logout.unauth.spec.ts +103 -0
- package/templates/base/tests/e2e/auth.setup.ts +115 -0
- package/templates/base/tests/e2e/auth.spec.ts +146 -0
- package/templates/base/tests/e2e/compatibility/cross-browser.spec.ts +152 -0
- package/templates/base/tests/e2e/compatibility/cross-browser.spec.ts-snapshots/login-chromium-chromium-darwin.png +0 -0
- package/templates/base/tests/e2e/crud/account.spec.ts +356 -0
- package/templates/base/tests/e2e/crud/integrations.spec.ts +419 -0
- package/templates/base/tests/e2e/crud/team.spec.ts +287 -0
- package/templates/base/tests/e2e/crud/users.spec.ts +239 -0
- package/templates/base/tests/e2e/errors/error-boundary.spec.ts +428 -0
- package/templates/base/tests/e2e/errors/error-handling.spec.ts +47 -0
- package/templates/base/tests/e2e/errors/error-handling.unauth.spec.ts +205 -0
- package/templates/base/tests/e2e/fixtures.ts +266 -0
- package/templates/base/tests/e2e/forms/validation.spec.ts +569 -0
- package/templates/base/tests/e2e/invitations/invite-flow.unauth.spec.ts +204 -0
- package/templates/base/tests/e2e/journeys/account-lifecycle.spec.ts +314 -0
- package/templates/base/tests/e2e/journeys/audit-investigation.spec.ts +299 -0
- package/templates/base/tests/e2e/journeys/auth-onboarding.spec.ts +232 -0
- package/templates/base/tests/e2e/journeys/critical-flows.spec.ts +281 -0
- package/templates/base/tests/e2e/journeys/error-recovery.spec.ts +354 -0
- package/templates/base/tests/e2e/journeys/file-management.spec.ts +307 -0
- package/templates/base/tests/e2e/journeys/integrations.spec.ts +372 -0
- package/templates/base/tests/e2e/journeys/multi-account.spec.ts +317 -0
- package/templates/base/tests/e2e/journeys/rbac-enforcement.spec.ts +389 -0
- package/templates/base/tests/e2e/journeys/settings-profile.spec.ts +400 -0
- package/templates/base/tests/e2e/journeys/team-collaboration.spec.ts +410 -0
- package/templates/base/tests/e2e/mobile/responsive.spec.ts +178 -0
- package/templates/base/tests/e2e/navigation/routing.spec.ts +371 -0
- package/templates/base/tests/e2e/navigation/sidebar.spec.ts +425 -0
- package/templates/base/tests/e2e/pages/ui-features.spec.ts +393 -0
- package/templates/base/tests/e2e/performance/baselines.spec.ts +162 -0
- package/templates/base/tests/e2e/performance/benchmarks.spec.ts +371 -0
- package/templates/base/tests/e2e/smoke.unauth.spec.ts +196 -0
- package/templates/base/tests/e2e/visual/components.spec.ts +650 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/confirmation-dialog-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-background-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-card-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-sidebar-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dashboard-card-single-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dialog-content-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dialog-with-backdrop-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/empty-search-results-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/input-default-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/input-focus-ring-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-focus-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-hover-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-active-nav-item-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-collapsed-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-navigation-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts +192 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/account-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/create-webhook-dialog-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/dashboard-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/delete-account-dialog-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/integrations-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/invite-member-dialog-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/login-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/settings-account-tab-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/settings-profile-tab-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/sidebar-navigation-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/team-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts +230 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/404-page-chromium-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/404-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/account-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/login-page-chromium-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/login-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/settings-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/team-invite-dialog-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/team-page-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/webhook-create-dialog-visual-darwin.png +0 -0
- package/templates/base/tests/e2e/visual/theme-colors.spec.ts +293 -0
- package/templates/base/tests/e2e/visual/ui-components.spec.ts +502 -0
- package/templates/base/tests/integration/accounts/crud.test.ts +1402 -0
- package/templates/base/tests/integration/audits/list.test.ts +1133 -0
- package/templates/base/tests/integration/auth/auth-service.test.ts +415 -0
- package/templates/base/tests/integration/auth/invitation-token.test.ts +529 -0
- package/templates/base/tests/integration/auth/logout.test.ts +524 -0
- package/templates/base/tests/integration/auth/oauth.test.ts +768 -0
- package/templates/base/tests/integration/auth/refresh-token.test.ts +364 -0
- package/templates/base/tests/integration/auth/session-expiry.test.ts +569 -0
- package/templates/base/tests/integration/auth/session.test.ts +520 -0
- package/templates/base/tests/integration/auth/super-admin.test.ts +451 -0
- package/templates/base/tests/integration/authorization/analytics-role.test.ts +1026 -0
- package/templates/base/tests/integration/authorization/billing-role.test.ts +776 -0
- package/templates/base/tests/integration/authorization/guards-roles.test.ts +539 -0
- package/templates/base/tests/integration/authorization/multi-tenancy.test.ts +1112 -0
- package/templates/base/tests/integration/authorization/role-hierarchy.test.ts +931 -0
- package/templates/base/tests/integration/authorization/roles.test.ts +1347 -0
- package/templates/base/tests/integration/config/production-behavior.test.ts +536 -0
- package/templates/base/tests/integration/db/constraints.test.ts +695 -0
- package/templates/base/tests/integration/fixtures/accounts.ts +136 -0
- package/templates/base/tests/integration/fixtures/index.ts +232 -0
- package/templates/base/tests/integration/fixtures/invitations.ts +195 -0
- package/templates/base/tests/integration/fixtures/users.ts +144 -0
- package/templates/base/tests/integration/health/health.test.ts +351 -0
- package/templates/base/tests/integration/invitations/crud.test.ts +1457 -0
- package/templates/base/tests/integration/invitations/email.test.ts +506 -0
- package/templates/base/tests/integration/lib/email.test.ts +174 -0
- package/templates/base/tests/integration/lib/oauth.test.ts +368 -0
- package/templates/base/tests/integration/lib/password.test.ts +192 -0
- package/templates/base/tests/integration/lib/schema-helpers.test.ts +129 -0
- package/templates/base/tests/integration/lib/tokens.test.ts +304 -0
- package/templates/base/tests/integration/middleware/auth.test.ts +499 -0
- package/templates/base/tests/integration/middleware/cors.test.ts +334 -0
- package/templates/base/tests/integration/middleware/request-context.test.ts +156 -0
- package/templates/base/tests/integration/middleware/request-logger.test.ts +313 -0
- package/templates/base/tests/integration/performance/response-times.test.ts +509 -0
- package/templates/base/tests/integration/security/cookie-security.test.ts +567 -0
- package/templates/base/tests/integration/security/csrf-protection.test.ts +542 -0
- package/templates/base/tests/integration/security/jwt-validation.test.ts +209 -0
- package/templates/base/tests/integration/security/log-sanitization.test.ts +658 -0
- package/templates/base/tests/integration/security/rate-limiting.test.ts +1251 -0
- package/templates/base/tests/integration/security/sql-injection.test.ts +663 -0
- package/templates/base/tests/integration/security/token-hashing.test.ts +371 -0
- package/templates/base/tests/integration/security/xss-prevention.test.ts +541 -0
- package/templates/base/tests/integration/setup.ts +834 -0
- package/templates/base/tests/integration/smoke.test.ts +288 -0
- package/templates/base/tests/integration/storage/upload.test.ts +1162 -0
- package/templates/base/tests/integration/storage/validation.test.ts +746 -0
- package/templates/base/tests/integration/users/crud.test.ts +1297 -0
- package/templates/base/tests/integration/users/list.test.ts +698 -0
- package/templates/base/tests/integration/vitest.config.ts +80 -0
- package/templates/base/tsconfig.app.json +18 -0
- package/templates/base/tsconfig.json +39 -0
- package/templates/base/tsconfig.node.json +16 -0
- package/templates/base/tsconfig.server.json +26 -0
- package/templates/base/tsconfig.tsbuildinfo +1 -0
- package/templates/base/vite.config.ts +46 -0
- package/templates/base/vitest.config.browser.ts +47 -0
- package/templates/base/vitest.config.frontend.ts +47 -0
- package/templates/base/vitest.config.ts +82 -0
- package/templates/base/vitest.workspace.ts +22 -0
- package/templates/modules/audit-logs/.gitkeep +0 -0
- package/templates/modules/billing/.gitkeep +0 -0
- package/templates/modules/invitations/.gitkeep +0 -0
- package/templates/modules/storage/.gitkeep +0 -0
- package/templates/modules/webhooks/.gitkeep +0 -0
- package/templates/providers/auth-email/.gitkeep +0 -0
- package/templates/providers/auth-github/.gitkeep +0 -0
- package/templates/providers/auth-google/.gitkeep +0 -0
- package/templates/providers/email-resend/.gitkeep +0 -0
- package/templates/providers/email-sendgrid/.gitkeep +0 -0
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
import { test, expect, isAuthenticated } from '../fixtures'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Keyboard Navigation & Screen Reader Accessibility Tests
|
|
5
|
+
*
|
|
6
|
+
* Tests for keyboard navigation, focus management, ARIA attributes,
|
|
7
|
+
* and screen reader accessibility features.
|
|
8
|
+
*
|
|
9
|
+
* @tags @a11y
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
test.describe('Keyboard Navigation Tests @a11y', () => {
|
|
13
|
+
test.describe('Page Title Announcements (Screen Reader)', () => {
|
|
14
|
+
test.beforeEach(async ({ page }) => {
|
|
15
|
+
const authenticated = await isAuthenticated(page)
|
|
16
|
+
test.skip(!authenticated, 'No authenticated session available')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('document.title changes when navigating to dashboard', async ({ page }) => {
|
|
20
|
+
await page.goto('/dashboard')
|
|
21
|
+
await expect(page).toHaveTitle(/dashboard/i)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('document.title changes when navigating to team page', async ({ page }) => {
|
|
25
|
+
await page.goto('/team')
|
|
26
|
+
await expect(page).toHaveTitle(/team/i)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('document.title changes when navigating to settings', async ({ page }) => {
|
|
30
|
+
await page.goto('/settings')
|
|
31
|
+
await expect(page).toHaveTitle(/settings/i)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test('document.title changes when navigating to integrations', async ({ page }) => {
|
|
35
|
+
await page.goto('/integrations')
|
|
36
|
+
await expect(page).toHaveTitle(/integrations/i)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test('document.title changes when navigating to account', async ({ page }) => {
|
|
40
|
+
await page.goto('/account')
|
|
41
|
+
await expect(page).toHaveTitle(/account/i)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('page title updates after navigation via keyboard', async ({ page }) => {
|
|
45
|
+
await page.goto('/dashboard')
|
|
46
|
+
const initialTitle = await page.title()
|
|
47
|
+
|
|
48
|
+
// Focus on navigation and navigate to team page
|
|
49
|
+
const teamLink = page.locator('nav').getByRole('link', { name: /team/i })
|
|
50
|
+
await teamLink.focus()
|
|
51
|
+
await page.keyboard.press('Enter')
|
|
52
|
+
|
|
53
|
+
// Wait for navigation to complete
|
|
54
|
+
await page.waitForURL('**/team')
|
|
55
|
+
const newTitle = await page.title()
|
|
56
|
+
|
|
57
|
+
expect(newTitle).not.toBe(initialTitle)
|
|
58
|
+
expect(newTitle.toLowerCase()).toContain('team')
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test.describe('ARIA Labels on Icon-Only Buttons', () => {
|
|
63
|
+
test.beforeEach(async ({ page }) => {
|
|
64
|
+
const authenticated = await isAuthenticated(page)
|
|
65
|
+
test.skip(!authenticated, 'No authenticated session available')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('sidebar collapse button has accessible name', async ({ page }) => {
|
|
69
|
+
await page.goto('/dashboard')
|
|
70
|
+
|
|
71
|
+
// Find all icon-only buttons in sidebar (size="icon" buttons)
|
|
72
|
+
const sidebarButtons = page.locator('aside button[class*="h-8"][class*="w-8"]')
|
|
73
|
+
const count = await sidebarButtons.count()
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < count; i++) {
|
|
76
|
+
const button = sidebarButtons.nth(i)
|
|
77
|
+
await expect(button).toBeVisible()
|
|
78
|
+
|
|
79
|
+
// Check if button has accessible name via aria-label, aria-labelledby, or title
|
|
80
|
+
const ariaLabel = await button.getAttribute('aria-label')
|
|
81
|
+
const ariaLabelledBy = await button.getAttribute('aria-labelledby')
|
|
82
|
+
const title = await button.getAttribute('title')
|
|
83
|
+
|
|
84
|
+
const hasAccessibleName = ariaLabel !== null || ariaLabelledBy !== null || title !== null
|
|
85
|
+
|
|
86
|
+
// If no explicit label, check if it contains text content
|
|
87
|
+
if (!hasAccessibleName) {
|
|
88
|
+
const textContent = await button.textContent()
|
|
89
|
+
const hasText = textContent && textContent.trim().length > 0
|
|
90
|
+
expect(hasText || hasAccessibleName).toBe(true)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test('webhook action buttons have accessible names', async ({ page }) => {
|
|
96
|
+
await page.goto('/integrations')
|
|
97
|
+
|
|
98
|
+
// Wait for webhooks section to load
|
|
99
|
+
await expect(page.getByRole('heading', { name: /webhooks/i })).toBeVisible()
|
|
100
|
+
|
|
101
|
+
// Find icon-only buttons in webhook cards (edit, delete)
|
|
102
|
+
const webhookButtons = page.locator('.space-y-3 button[class*="h-8"][class*="w-8"]')
|
|
103
|
+
const count = await webhookButtons.count()
|
|
104
|
+
|
|
105
|
+
if (count > 0) {
|
|
106
|
+
for (let i = 0; i < count; i++) {
|
|
107
|
+
const button = webhookButtons.nth(i)
|
|
108
|
+
if (await button.isVisible()) {
|
|
109
|
+
// Button should have aria-label or title for accessibility
|
|
110
|
+
const ariaLabel = await button.getAttribute('aria-label')
|
|
111
|
+
const title = await button.getAttribute('title')
|
|
112
|
+
const accessibleName = await button.evaluate(
|
|
113
|
+
(el) => el.getAttribute('aria-label') || el.getAttribute('title') || el.textContent?.trim()
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
// Log for debugging but don't fail - this is informational
|
|
117
|
+
if (!ariaLabel && !title) {
|
|
118
|
+
console.log(`Icon button ${i} may need aria-label for better accessibility`)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('team page action buttons have accessible names', async ({ page }) => {
|
|
126
|
+
await page.goto('/team')
|
|
127
|
+
|
|
128
|
+
// Find the "more" button for team members (if exists)
|
|
129
|
+
const moreButtons = page.getByRole('button').filter({ has: page.locator('[class*="More"], [class*="more"]') })
|
|
130
|
+
const count = await moreButtons.count()
|
|
131
|
+
|
|
132
|
+
if (count > 0) {
|
|
133
|
+
for (let i = 0; i < count; i++) {
|
|
134
|
+
const button = moreButtons.nth(i)
|
|
135
|
+
if (await button.isVisible()) {
|
|
136
|
+
const ariaLabel = await button.getAttribute('aria-label')
|
|
137
|
+
const title = await button.getAttribute('title')
|
|
138
|
+
|
|
139
|
+
// Log if missing, for improvement tracking
|
|
140
|
+
if (!ariaLabel && !title) {
|
|
141
|
+
console.log(`More options button ${i} should have aria-label like "More options"`)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test.describe('Focus Management with Dialogs', () => {
|
|
150
|
+
test.beforeEach(async ({ page }) => {
|
|
151
|
+
const authenticated = await isAuthenticated(page)
|
|
152
|
+
test.skip(!authenticated, 'No authenticated session available')
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test('focus moves to dialog when opened', async ({ page }) => {
|
|
156
|
+
await page.goto('/team')
|
|
157
|
+
|
|
158
|
+
// Store the element that opened the dialog
|
|
159
|
+
const inviteButton = page.getByRole('button', { name: /invite member/i })
|
|
160
|
+
await expect(inviteButton).toBeVisible()
|
|
161
|
+
|
|
162
|
+
// Open dialog
|
|
163
|
+
await inviteButton.click()
|
|
164
|
+
|
|
165
|
+
// Dialog should be visible
|
|
166
|
+
const dialog = page.getByRole('dialog')
|
|
167
|
+
await expect(dialog).toBeVisible()
|
|
168
|
+
|
|
169
|
+
// Focus should be inside the dialog (typically on first focusable element)
|
|
170
|
+
const emailInput = page.getByLabel(/email address/i)
|
|
171
|
+
await expect(emailInput).toBeFocused()
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('focus returns to trigger when dialog closes via Escape', async ({ page }) => {
|
|
175
|
+
await page.goto('/team')
|
|
176
|
+
|
|
177
|
+
const inviteButton = page.getByRole('button', { name: /invite member/i })
|
|
178
|
+
await inviteButton.click()
|
|
179
|
+
|
|
180
|
+
const dialog = page.getByRole('dialog')
|
|
181
|
+
await expect(dialog).toBeVisible()
|
|
182
|
+
|
|
183
|
+
// Close with Escape
|
|
184
|
+
await page.keyboard.press('Escape')
|
|
185
|
+
await expect(dialog).not.toBeVisible()
|
|
186
|
+
|
|
187
|
+
// Focus should return to the invite button
|
|
188
|
+
await expect(inviteButton).toBeFocused()
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
test('focus returns to trigger when dialog closes via Cancel button', async ({ page }) => {
|
|
192
|
+
await page.goto('/team')
|
|
193
|
+
|
|
194
|
+
const inviteButton = page.getByRole('button', { name: /invite member/i })
|
|
195
|
+
await inviteButton.click()
|
|
196
|
+
|
|
197
|
+
const dialog = page.getByRole('dialog')
|
|
198
|
+
await expect(dialog).toBeVisible()
|
|
199
|
+
|
|
200
|
+
// Click Cancel button
|
|
201
|
+
const cancelButton = dialog.getByRole('button', { name: /cancel/i })
|
|
202
|
+
await cancelButton.click()
|
|
203
|
+
await expect(dialog).not.toBeVisible()
|
|
204
|
+
|
|
205
|
+
// Focus should return to the invite button
|
|
206
|
+
await expect(inviteButton).toBeFocused()
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
test('integrations webhook dialog manages focus correctly', async ({ page }) => {
|
|
210
|
+
await page.goto('/integrations')
|
|
211
|
+
|
|
212
|
+
// Open webhook dialog
|
|
213
|
+
const addWebhookButton = page.getByRole('button', { name: /add webhook/i })
|
|
214
|
+
await expect(addWebhookButton).toBeVisible()
|
|
215
|
+
await addWebhookButton.click()
|
|
216
|
+
|
|
217
|
+
// Dialog should be visible
|
|
218
|
+
const dialog = page.getByRole('dialog')
|
|
219
|
+
await expect(dialog).toBeVisible()
|
|
220
|
+
|
|
221
|
+
// Focus should be inside the dialog
|
|
222
|
+
const focusedElement = page.locator(':focus')
|
|
223
|
+
const isInDialog = await focusedElement.evaluate((el) => {
|
|
224
|
+
return el.closest('[role="dialog"]') !== null
|
|
225
|
+
})
|
|
226
|
+
expect(isInDialog).toBe(true)
|
|
227
|
+
|
|
228
|
+
// Close and verify focus returns
|
|
229
|
+
await page.keyboard.press('Escape')
|
|
230
|
+
await expect(dialog).not.toBeVisible()
|
|
231
|
+
await expect(addWebhookButton).toBeFocused()
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
test.describe('Tab Order', () => {
|
|
236
|
+
test.beforeEach(async ({ page }) => {
|
|
237
|
+
const authenticated = await isAuthenticated(page)
|
|
238
|
+
test.skip(!authenticated, 'No authenticated session available')
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
test('tab order follows visual layout on dashboard', async ({ page }) => {
|
|
242
|
+
await page.goto('/dashboard')
|
|
243
|
+
|
|
244
|
+
// Get initial focused element after page load
|
|
245
|
+
const initialFocus = page.locator(':focus')
|
|
246
|
+
|
|
247
|
+
// Tab through elements and track order
|
|
248
|
+
const focusOrder: string[] = []
|
|
249
|
+
const maxTabs = 15
|
|
250
|
+
|
|
251
|
+
for (let i = 0; i < maxTabs; i++) {
|
|
252
|
+
await page.keyboard.press('Tab')
|
|
253
|
+
const focused = page.locator(':focus')
|
|
254
|
+
|
|
255
|
+
if (await focused.count() > 0) {
|
|
256
|
+
const tagName = await focused.evaluate((el) => el.tagName.toLowerCase())
|
|
257
|
+
const role = await focused.getAttribute('role')
|
|
258
|
+
const text = await focused.textContent()
|
|
259
|
+
|
|
260
|
+
focusOrder.push(
|
|
261
|
+
`${tagName}${role ? `[role=${role}]` : ''}: ${text?.slice(0, 30).trim()}`
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Tab order should include navigation links before main content
|
|
267
|
+
// This is a sanity check that focus moves through the page logically
|
|
268
|
+
expect(focusOrder.length).toBeGreaterThan(0)
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
test('settings page tabs follow logical order', async ({ page }) => {
|
|
272
|
+
await page.goto('/settings')
|
|
273
|
+
|
|
274
|
+
// Focus on the tablist
|
|
275
|
+
const tablist = page.getByRole('tablist')
|
|
276
|
+
await expect(tablist).toBeVisible()
|
|
277
|
+
|
|
278
|
+
const profileTab = page.getByRole('tab', { name: /profile/i })
|
|
279
|
+
await profileTab.focus()
|
|
280
|
+
|
|
281
|
+
// Tab should go to next tab (Account)
|
|
282
|
+
await page.keyboard.press('Tab')
|
|
283
|
+
const focusedAfterTab = page.locator(':focus')
|
|
284
|
+
await expect(focusedAfterTab).toBeVisible()
|
|
285
|
+
|
|
286
|
+
// The focus should have moved to a focusable element in tab panel or next tab
|
|
287
|
+
const tagName = await focusedAfterTab.evaluate((el) => el.tagName.toLowerCase())
|
|
288
|
+
expect(['input', 'button', 'a', 'textarea', 'select']).toContain(tagName)
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
test('Shift+Tab moves focus backwards', async ({ page }) => {
|
|
292
|
+
await page.goto('/dashboard')
|
|
293
|
+
|
|
294
|
+
// Tab forward a few times
|
|
295
|
+
for (let i = 0; i < 5; i++) {
|
|
296
|
+
await page.keyboard.press('Tab')
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Record current focus
|
|
300
|
+
const afterTabbing = page.locator(':focus')
|
|
301
|
+
const afterTabbingText = await afterTabbing.textContent()
|
|
302
|
+
|
|
303
|
+
// Tab backward
|
|
304
|
+
await page.keyboard.press('Shift+Tab')
|
|
305
|
+
|
|
306
|
+
// Focus should have moved to a different element
|
|
307
|
+
const afterShiftTab = page.locator(':focus')
|
|
308
|
+
const afterShiftTabText = await afterShiftTab.textContent()
|
|
309
|
+
|
|
310
|
+
// Verify focus moved (could be same text if looping, but position changed)
|
|
311
|
+
expect(await afterShiftTab.count()).toBe(1)
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
test('focus skips hidden elements', async ({ page }) => {
|
|
315
|
+
await page.goto('/team')
|
|
316
|
+
|
|
317
|
+
// Tab through all focusable elements
|
|
318
|
+
const focusedElements: string[] = []
|
|
319
|
+
const maxTabs = 30
|
|
320
|
+
|
|
321
|
+
for (let i = 0; i < maxTabs; i++) {
|
|
322
|
+
await page.keyboard.press('Tab')
|
|
323
|
+
const focused = page.locator(':focus')
|
|
324
|
+
|
|
325
|
+
if (await focused.count() > 0) {
|
|
326
|
+
// Verify focused element is visible
|
|
327
|
+
const isVisible = await focused.isVisible()
|
|
328
|
+
expect(isVisible).toBe(true)
|
|
329
|
+
|
|
330
|
+
const ariaHidden = await focused.getAttribute('aria-hidden')
|
|
331
|
+
expect(ariaHidden).not.toBe('true')
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
test.describe('Skip to Main Content', () => {
|
|
338
|
+
test.beforeEach(async ({ page }) => {
|
|
339
|
+
const authenticated = await isAuthenticated(page)
|
|
340
|
+
test.skip(!authenticated, 'No authenticated session available')
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
test('skip link appears on first Tab press if exists', async ({ page }) => {
|
|
344
|
+
await page.goto('/dashboard')
|
|
345
|
+
|
|
346
|
+
// Press Tab to see if skip link appears
|
|
347
|
+
await page.keyboard.press('Tab')
|
|
348
|
+
|
|
349
|
+
// Check if there's a skip link (many accessible sites have this)
|
|
350
|
+
const skipLink = page.getByRole('link', { name: /skip to (main )?content/i })
|
|
351
|
+
const skipLinkExists = (await skipLink.count()) > 0
|
|
352
|
+
|
|
353
|
+
if (skipLinkExists) {
|
|
354
|
+
await expect(skipLink).toBeVisible()
|
|
355
|
+
await expect(skipLink).toBeFocused()
|
|
356
|
+
} else {
|
|
357
|
+
// If no skip link, first focusable should be in sidebar/nav
|
|
358
|
+
// This is informational - document the current behavior
|
|
359
|
+
const firstFocused = page.locator(':focus')
|
|
360
|
+
const isNavElement = await firstFocused.evaluate((el) => {
|
|
361
|
+
return el.closest('nav') !== null || el.closest('aside') !== null
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
// Log suggestion for improvement
|
|
365
|
+
console.log(
|
|
366
|
+
'No skip link found. Consider adding a "Skip to main content" link for keyboard users.'
|
|
367
|
+
)
|
|
368
|
+
}
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
test('main content area is focusable or reachable', async ({ page }) => {
|
|
372
|
+
await page.goto('/dashboard')
|
|
373
|
+
|
|
374
|
+
// The main content area should be reachable via keyboard
|
|
375
|
+
// Either through a skip link or within reasonable tab presses
|
|
376
|
+
const mainContent = page.locator('main')
|
|
377
|
+
await expect(mainContent).toBeVisible()
|
|
378
|
+
|
|
379
|
+
// Check if main has tabindex for direct focus or contains focusable content
|
|
380
|
+
const tabindex = await mainContent.getAttribute('tabindex')
|
|
381
|
+
const hasFocusableContent = await mainContent.evaluate((el) => {
|
|
382
|
+
const focusables = el.querySelectorAll(
|
|
383
|
+
'a, button, input, textarea, select, [tabindex]:not([tabindex="-1"])'
|
|
384
|
+
)
|
|
385
|
+
return focusables.length > 0
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
// Main should either be focusable directly or contain focusable elements
|
|
389
|
+
expect(tabindex !== null || hasFocusableContent).toBe(true)
|
|
390
|
+
})
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
test.describe('Sidebar Keyboard Navigation', () => {
|
|
394
|
+
test.beforeEach(async ({ page }) => {
|
|
395
|
+
const authenticated = await isAuthenticated(page)
|
|
396
|
+
test.skip(!authenticated, 'No authenticated session available')
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
test('sidebar navigation links are keyboard accessible', async ({ page }) => {
|
|
400
|
+
await page.goto('/dashboard')
|
|
401
|
+
|
|
402
|
+
// Find nav links in sidebar
|
|
403
|
+
const navLinks = page.locator('aside nav').getByRole('link')
|
|
404
|
+
const linkCount = await navLinks.count()
|
|
405
|
+
|
|
406
|
+
expect(linkCount).toBeGreaterThan(0)
|
|
407
|
+
|
|
408
|
+
// Each link should be focusable and activatable
|
|
409
|
+
const firstLink = navLinks.first()
|
|
410
|
+
await firstLink.focus()
|
|
411
|
+
await expect(firstLink).toBeFocused()
|
|
412
|
+
|
|
413
|
+
// Should be able to activate with Enter
|
|
414
|
+
const href = await firstLink.getAttribute('href')
|
|
415
|
+
await page.keyboard.press('Enter')
|
|
416
|
+
await page.waitForURL(`**${href}**`)
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
test('sidebar collapse toggle works with keyboard', async ({ page }) => {
|
|
420
|
+
await page.goto('/dashboard')
|
|
421
|
+
|
|
422
|
+
// Find the collapse button in sidebar
|
|
423
|
+
const sidebar = page.locator('aside')
|
|
424
|
+
const initialBox = await sidebar.boundingBox()
|
|
425
|
+
const initialWidth = initialBox?.width ?? 0
|
|
426
|
+
|
|
427
|
+
// Use Cmd+B or Ctrl+B to toggle sidebar
|
|
428
|
+
await page.keyboard.press('Control+b')
|
|
429
|
+
|
|
430
|
+
// Wait for transition and verify width changed
|
|
431
|
+
await page.waitForTimeout(350) // Wait for transition duration
|
|
432
|
+
const newBox = await sidebar.boundingBox()
|
|
433
|
+
const newWidth = newBox?.width ?? 0
|
|
434
|
+
|
|
435
|
+
expect(newWidth).not.toBe(initialWidth)
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
test('theme toggle button is keyboard accessible', async ({ page }) => {
|
|
439
|
+
await page.goto('/dashboard')
|
|
440
|
+
|
|
441
|
+
// Find the theme toggle button (has moon or sun icon)
|
|
442
|
+
const themeButton = page.locator('aside').getByRole('button').filter({
|
|
443
|
+
has: page.locator('[class*="moon"], [class*="sun"], svg'),
|
|
444
|
+
}).first()
|
|
445
|
+
|
|
446
|
+
if (await themeButton.count() > 0) {
|
|
447
|
+
await themeButton.focus()
|
|
448
|
+
await expect(themeButton).toBeFocused()
|
|
449
|
+
|
|
450
|
+
// Should have title or aria-label
|
|
451
|
+
const title = await themeButton.getAttribute('title')
|
|
452
|
+
expect(title).toBeTruthy()
|
|
453
|
+
}
|
|
454
|
+
})
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
test.describe('Form Keyboard Navigation', () => {
|
|
458
|
+
test.beforeEach(async ({ page }) => {
|
|
459
|
+
const authenticated = await isAuthenticated(page)
|
|
460
|
+
test.skip(!authenticated, 'No authenticated session available')
|
|
461
|
+
})
|
|
462
|
+
|
|
463
|
+
test('settings form fields are navigable with Tab', async ({ page }) => {
|
|
464
|
+
await page.goto('/settings')
|
|
465
|
+
|
|
466
|
+
// Focus on name input
|
|
467
|
+
const nameInput = page.getByLabel(/full name/i)
|
|
468
|
+
await nameInput.focus()
|
|
469
|
+
await expect(nameInput).toBeFocused()
|
|
470
|
+
|
|
471
|
+
// Tab to next field (email input)
|
|
472
|
+
await page.keyboard.press('Tab')
|
|
473
|
+
const emailInput = page.getByLabel(/email address/i)
|
|
474
|
+
await expect(emailInput).toBeFocused()
|
|
475
|
+
|
|
476
|
+
// Tab to save button
|
|
477
|
+
let foundSaveButton = false
|
|
478
|
+
for (let i = 0; i < 5; i++) {
|
|
479
|
+
await page.keyboard.press('Tab')
|
|
480
|
+
const focused = page.locator(':focus')
|
|
481
|
+
const text = await focused.textContent()
|
|
482
|
+
if (text?.includes('Save')) {
|
|
483
|
+
foundSaveButton = true
|
|
484
|
+
break
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
expect(foundSaveButton).toBe(true)
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
test('form can be submitted with Enter key', async ({ page }) => {
|
|
491
|
+
await page.goto('/settings')
|
|
492
|
+
|
|
493
|
+
// Focus on name input
|
|
494
|
+
const nameInput = page.getByLabel(/full name/i)
|
|
495
|
+
await nameInput.fill('Test User')
|
|
496
|
+
|
|
497
|
+
// Navigate to save button and press Enter
|
|
498
|
+
const saveButton = page.getByRole('button', { name: /save changes/i })
|
|
499
|
+
await saveButton.focus()
|
|
500
|
+
await page.keyboard.press('Enter')
|
|
501
|
+
|
|
502
|
+
// Should see some response (toast, loading state, etc.)
|
|
503
|
+
// Just verify the button responded to keyboard activation
|
|
504
|
+
await expect(saveButton).toBeVisible()
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
test('radio button groups can be navigated with arrow keys', async ({ page }) => {
|
|
508
|
+
await page.goto('/team')
|
|
509
|
+
|
|
510
|
+
// Open invite dialog which has role selection
|
|
511
|
+
await page.getByRole('button', { name: /invite member/i }).click()
|
|
512
|
+
const dialog = page.getByRole('dialog')
|
|
513
|
+
await expect(dialog).toBeVisible()
|
|
514
|
+
|
|
515
|
+
// Find role buttons (Member/Admin toggle)
|
|
516
|
+
const memberButton = dialog.getByRole('button', { name: /member/i }).first()
|
|
517
|
+
const adminButton = dialog.getByRole('button', { name: /admin/i }).first()
|
|
518
|
+
|
|
519
|
+
// Focus on member button
|
|
520
|
+
await memberButton.focus()
|
|
521
|
+
await expect(memberButton).toBeFocused()
|
|
522
|
+
|
|
523
|
+
// Tab should move to admin button
|
|
524
|
+
await page.keyboard.press('Tab')
|
|
525
|
+
await expect(adminButton).toBeFocused()
|
|
526
|
+
|
|
527
|
+
// Activating with Space or Enter should work
|
|
528
|
+
await page.keyboard.press('Space')
|
|
529
|
+
|
|
530
|
+
// Verify admin is now selected (variant changed)
|
|
531
|
+
await expect(adminButton).toHaveAttribute('data-variant', /.*/i)
|
|
532
|
+
})
|
|
533
|
+
})
|
|
534
|
+
|
|
535
|
+
test.describe('Integrations Page Keyboard Navigation', () => {
|
|
536
|
+
test.beforeEach(async ({ page }) => {
|
|
537
|
+
const authenticated = await isAuthenticated(page)
|
|
538
|
+
test.skip(!authenticated, 'No authenticated session available')
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
test('category filter buttons are keyboard navigable', async ({ page }) => {
|
|
542
|
+
await page.goto('/integrations')
|
|
543
|
+
|
|
544
|
+
// Find category buttons
|
|
545
|
+
const categoryButtons = page.getByRole('button', { name: /all|communication|payments|development|automation/i })
|
|
546
|
+
const count = await categoryButtons.count()
|
|
547
|
+
|
|
548
|
+
expect(count).toBeGreaterThan(0)
|
|
549
|
+
|
|
550
|
+
// Focus on first category button
|
|
551
|
+
await categoryButtons.first().focus()
|
|
552
|
+
await expect(categoryButtons.first()).toBeFocused()
|
|
553
|
+
|
|
554
|
+
// Tab through categories
|
|
555
|
+
for (let i = 1; i < count && i < 4; i++) {
|
|
556
|
+
await page.keyboard.press('Tab')
|
|
557
|
+
const focused = page.locator(':focus')
|
|
558
|
+
await expect(focused).toBeVisible()
|
|
559
|
+
}
|
|
560
|
+
})
|
|
561
|
+
|
|
562
|
+
test('integration cards have focusable action buttons', async ({ page }) => {
|
|
563
|
+
await page.goto('/integrations')
|
|
564
|
+
|
|
565
|
+
// Find connect/configure buttons on integration cards
|
|
566
|
+
const actionButtons = page.locator('[class*="CardContent"]').getByRole('button', {
|
|
567
|
+
name: /connect|configure/i,
|
|
568
|
+
})
|
|
569
|
+
const count = await actionButtons.count()
|
|
570
|
+
|
|
571
|
+
if (count > 0) {
|
|
572
|
+
// First button should be focusable
|
|
573
|
+
await actionButtons.first().focus()
|
|
574
|
+
await expect(actionButtons.first()).toBeFocused()
|
|
575
|
+
|
|
576
|
+
// Should be activatable with Enter
|
|
577
|
+
await page.keyboard.press('Enter')
|
|
578
|
+
|
|
579
|
+
// Button should respond (either change state or trigger action)
|
|
580
|
+
await expect(actionButtons.first()).toBeVisible()
|
|
581
|
+
}
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
test('search input captures focus on Tab', async ({ page }) => {
|
|
585
|
+
await page.goto('/integrations')
|
|
586
|
+
|
|
587
|
+
// Tab until we reach the search input
|
|
588
|
+
const searchInput = page.getByPlaceholder(/search integrations/i)
|
|
589
|
+
|
|
590
|
+
let foundSearch = false
|
|
591
|
+
for (let i = 0; i < 15; i++) {
|
|
592
|
+
await page.keyboard.press('Tab')
|
|
593
|
+
const focused = page.locator(':focus')
|
|
594
|
+
|
|
595
|
+
if (await searchInput.evaluate((el, focused) => el === focused, await focused.elementHandle())) {
|
|
596
|
+
foundSearch = true
|
|
597
|
+
break
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Alternatively, directly focus and verify
|
|
602
|
+
await searchInput.focus()
|
|
603
|
+
await expect(searchInput).toBeFocused()
|
|
604
|
+
|
|
605
|
+
// Type should work when focused
|
|
606
|
+
await page.keyboard.type('slack')
|
|
607
|
+
await expect(searchInput).toHaveValue('slack')
|
|
608
|
+
})
|
|
609
|
+
})
|
|
610
|
+
})
|