@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.
Files changed (688) hide show
  1. package/dist/cli.d.ts +13 -0
  2. package/dist/cli.js +46 -0
  3. package/dist/cli.js.map +1 -0
  4. package/dist/cli.test.d.ts +1 -0
  5. package/dist/cli.test.js +26 -0
  6. package/dist/cli.test.js.map +1 -0
  7. package/dist/generator.d.ts +14 -0
  8. package/dist/generator.js +142 -0
  9. package/dist/generator.js.map +1 -0
  10. package/dist/generator.test.d.ts +1 -0
  11. package/dist/generator.test.js +127 -0
  12. package/dist/generator.test.js.map +1 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +97 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/prompts.d.ts +25 -0
  17. package/dist/prompts.js +83 -0
  18. package/dist/prompts.js.map +1 -0
  19. package/dist/prompts.test.d.ts +1 -0
  20. package/dist/prompts.test.js +24 -0
  21. package/dist/prompts.test.js.map +1 -0
  22. package/dist/providers/cloudflare.d.ts +37 -0
  23. package/dist/providers/cloudflare.js +61 -0
  24. package/dist/providers/cloudflare.js.map +1 -0
  25. package/dist/providers/cloudflare.test.d.ts +1 -0
  26. package/dist/providers/cloudflare.test.js +29 -0
  27. package/dist/providers/cloudflare.test.js.map +1 -0
  28. package/dist/providers/github.d.ts +16 -0
  29. package/dist/providers/github.js +57 -0
  30. package/dist/providers/github.js.map +1 -0
  31. package/dist/providers/github.test.d.ts +1 -0
  32. package/dist/providers/github.test.js +16 -0
  33. package/dist/providers/github.test.js.map +1 -0
  34. package/dist/templates.d.ts +8 -0
  35. package/dist/templates.js +88 -0
  36. package/dist/templates.js.map +1 -0
  37. package/dist/templates.test.d.ts +1 -0
  38. package/dist/templates.test.js +26 -0
  39. package/dist/templates.test.js.map +1 -0
  40. package/package.json +36 -0
  41. package/templates/base/.claude/agents/architect-review.md +160 -0
  42. package/templates/base/.claude/agents/backend-architect.md +308 -0
  43. package/templates/base/.claude/agents/code-reviewer.md +170 -0
  44. package/templates/base/.claude/agents/performance-engineer.md +166 -0
  45. package/templates/base/.claude/agents/test-automator.md +219 -0
  46. package/templates/base/.claude/commands/check-skill-rules.md +53 -0
  47. package/templates/base/.claude/commands/claude-md.md +250 -0
  48. package/templates/base/.claude/commands/code-prompt.md +212 -0
  49. package/templates/base/.claude/commands/explain-code.md +194 -0
  50. package/templates/base/.claude/commands/init-projec.md +89 -0
  51. package/templates/base/.claude/commands/linear/README.md +297 -0
  52. package/templates/base/.claude/commands/linear/create-issue.md +190 -0
  53. package/templates/base/.claude/commands/linear/implement-issue.md +248 -0
  54. package/templates/base/.claude/commands/linear/process-triage.md +399 -0
  55. package/templates/base/.claude/commands/linear/setup.md +180 -0
  56. package/templates/base/.claude/commands/prime.md +9 -0
  57. package/templates/base/.claude/commands/review-gap.md +10 -0
  58. package/templates/base/.claude/commands/setup-aa.md +311 -0
  59. package/templates/base/.claude/commands/ship.md +262 -0
  60. package/templates/base/.claude/commands/tools.md +3 -0
  61. package/templates/base/.claude/docs/claude-progress.txt +107 -0
  62. package/templates/base/.claude/hooks/package-lock.json +556 -0
  63. package/templates/base/.claude/hooks/package.json +16 -0
  64. package/templates/base/.claude/hooks/skill-activation-prompt.sh +7 -0
  65. package/templates/base/.claude/hooks/skill-activation-prompt.ts +142 -0
  66. package/templates/base/.claude/hooks/tsconfig.json +19 -0
  67. package/templates/base/.claude/scripts/check-updates.sh +85 -0
  68. package/templates/base/.claude/scripts/install_pkgs.sh +66 -0
  69. package/templates/base/.claude/scripts/setup-project.sh +177 -0
  70. package/templates/base/.claude/scripts/validate-skill-rules.sh +94 -0
  71. package/templates/base/.claude/settings.json +113 -0
  72. package/templates/base/.claude/settings.local.json +11 -0
  73. package/templates/base/.claude/skills/architecture-analyzer/SKILL.md +531 -0
  74. package/templates/base/.claude/skills/architecture-analyzer/assets/report-template.md +215 -0
  75. package/templates/base/.claude/skills/architecture-analyzer/references/c4-templates.md +234 -0
  76. package/templates/base/.claude/skills/architecture-analyzer/references/confidence-levels.md +203 -0
  77. package/templates/base/.claude/skills/architecture-analyzer/scripts/analyze_structure.py +266 -0
  78. package/templates/base/.claude/skills/architecture-analyzer/scripts/analyze_tech_debt.py +776 -0
  79. package/templates/base/.claude/skills/architecture-analyzer/scripts/extract_apis.py +338 -0
  80. package/templates/base/.claude/skills/architecture-analyzer/scripts/generate_c4.py +283 -0
  81. package/templates/base/.claude/skills/architecture-analyzer/scripts/generate_erd.py +935 -0
  82. package/templates/base/.claude/skills/architecture-analyzer/scripts/map_dependencies.py +555 -0
  83. package/templates/base/.claude/skills/dev-browser/SKILL.md +318 -0
  84. package/templates/base/.claude/skills/dev-browser/bun.lock +443 -0
  85. package/templates/base/.claude/skills/dev-browser/package-lock.json +2927 -0
  86. package/templates/base/.claude/skills/dev-browser/package.json +27 -0
  87. package/templates/base/.claude/skills/dev-browser/scripts/start-server.ts +117 -0
  88. package/templates/base/.claude/skills/dev-browser/server.sh +24 -0
  89. package/templates/base/.claude/skills/dev-browser/src/client.ts +403 -0
  90. package/templates/base/.claude/skills/dev-browser/src/index.ts +281 -0
  91. package/templates/base/.claude/skills/dev-browser/src/snapshot/__tests__/snapshot.test.ts +223 -0
  92. package/templates/base/.claude/skills/dev-browser/src/snapshot/browser-script.ts +877 -0
  93. package/templates/base/.claude/skills/dev-browser/src/snapshot/index.ts +14 -0
  94. package/templates/base/.claude/skills/dev-browser/src/snapshot/inject.ts +13 -0
  95. package/templates/base/.claude/skills/dev-browser/src/types.ts +27 -0
  96. package/templates/base/.claude/skills/dev-browser/tsconfig.json +36 -0
  97. package/templates/base/.claude/skills/dev-browser/vitest.config.ts +12 -0
  98. package/templates/base/.claude/skills/linear/SKILL.md +440 -0
  99. package/templates/base/.claude/skills/linear/examples.md +262 -0
  100. package/templates/base/.claude/skills/linear/lib/client.ts +51 -0
  101. package/templates/base/.claude/skills/linear/lib/config.ts +106 -0
  102. package/templates/base/.claude/skills/linear/lib/output.ts +34 -0
  103. package/templates/base/.claude/skills/linear/package-lock.json +698 -0
  104. package/templates/base/.claude/skills/linear/package.json +27 -0
  105. package/templates/base/.claude/skills/linear/reference.md +263 -0
  106. package/templates/base/.claude/skills/linear/scripts/comments/create.ts +47 -0
  107. package/templates/base/.claude/skills/linear/scripts/comments/list.ts +47 -0
  108. package/templates/base/.claude/skills/linear/scripts/issues/archive.ts +30 -0
  109. package/templates/base/.claude/skills/linear/scripts/issues/create.ts +279 -0
  110. package/templates/base/.claude/skills/linear/scripts/issues/get.ts +68 -0
  111. package/templates/base/.claude/skills/linear/scripts/issues/list.ts +67 -0
  112. package/templates/base/.claude/skills/linear/scripts/issues/update.ts +281 -0
  113. package/templates/base/.claude/skills/linear/scripts/labels/add-to-issue.ts +63 -0
  114. package/templates/base/.claude/skills/linear/scripts/labels/create.ts +45 -0
  115. package/templates/base/.claude/skills/linear/scripts/labels/list.ts +30 -0
  116. package/templates/base/.claude/skills/linear/scripts/list-teams.ts +52 -0
  117. package/templates/base/.claude/skills/linear/scripts/setup/setup-credentials.ts +96 -0
  118. package/templates/base/.claude/skills/linear/scripts/status/list.ts +31 -0
  119. package/templates/base/.claude/skills/linear/scripts/status/set-by-name.ts +78 -0
  120. package/templates/base/.claude/skills/linear/scripts/status/update.ts +44 -0
  121. package/templates/base/.claude/skills/linear/scripts/users/list.ts +59 -0
  122. package/templates/base/.claude/skills/linear/scripts/users/me.ts +20 -0
  123. package/templates/base/.claude/skills/linear/templates/README.md +203 -0
  124. package/templates/base/.claude/skills/linear/templates/api-reference.md +258 -0
  125. package/templates/base/.claude/skills/linear/templates/bug-report.md +99 -0
  126. package/templates/base/.claude/skills/linear/templates/feature-request.md +118 -0
  127. package/templates/base/.claude/skills/linear/templates/security-issue.md +162 -0
  128. package/templates/base/.claude/skills/linear/templates/sprint-task.md +175 -0
  129. package/templates/base/.claude/skills/linear/templates/tech-debt.md +137 -0
  130. package/templates/base/.claude/skills/linear/tsconfig.json +17 -0
  131. package/templates/base/.claude/skills/linear/workflows/issue-lifecycle.md +317 -0
  132. package/templates/base/.claude/skills/playwright-e2e-testing/SKILL.md +113 -0
  133. package/templates/base/.claude/skills/playwright-e2e-testing/assets/global-setup.template.js +97 -0
  134. package/templates/base/.claude/skills/playwright-e2e-testing/assets/playwright.config.template.js +171 -0
  135. package/templates/base/.claude/skills/playwright-e2e-testing/assets/test-template.spec.js +163 -0
  136. package/templates/base/.claude/skills/playwright-e2e-testing/examples/README.md +26 -0
  137. package/templates/base/.claude/skills/playwright-e2e-testing/examples/ads.email-deeplink.spec.ts +12 -0
  138. package/templates/base/.claude/skills/playwright-e2e-testing/examples/mobile.realism.spec.ts +16 -0
  139. package/templates/base/.claude/skills/playwright-e2e-testing/examples/smoke.home.spec.ts +6 -0
  140. package/templates/base/.claude/skills/playwright-e2e-testing/references/architecture.md +578 -0
  141. package/templates/base/.claude/skills/playwright-e2e-testing/references/best-practices.md +260 -0
  142. package/templates/base/.claude/skills/playwright-e2e-testing/references/ci-reporting.md +86 -0
  143. package/templates/base/.claude/skills/playwright-e2e-testing/references/debugging.md +629 -0
  144. package/templates/base/.claude/skills/playwright-e2e-testing/references/mobile-realism.md +50 -0
  145. package/templates/base/.claude/skills/playwright-e2e-testing/references/optimization.md +488 -0
  146. package/templates/base/.claude/skills/playwright-e2e-testing/references/patterns.md +513 -0
  147. package/templates/base/.claude/skills/playwright-e2e-testing/references/resources.md +44 -0
  148. package/templates/base/.claude/skills/playwright-e2e-testing/references/visual-a11y.md +66 -0
  149. package/templates/base/.claude/skills/playwright-e2e-testing/scripts/auth-setup.js +202 -0
  150. package/templates/base/.claude/skills/playwright-e2e-testing/scripts/performance-analyzer.js +240 -0
  151. package/templates/base/.claude/skills/playwright-e2e-testing/scripts/trace-url.js +132 -0
  152. package/templates/base/.claude/skills/playwright-e2e-testing/templates/ci/github-actions.playwright.yml +78 -0
  153. package/templates/base/.claude/skills/playwright-e2e-testing/templates/fixtures.ts +44 -0
  154. package/templates/base/.claude/skills/playwright-e2e-testing/templates/global-setup.template.js +97 -0
  155. package/templates/base/.claude/skills/playwright-e2e-testing/templates/global.setup.ts +35 -0
  156. package/templates/base/.claude/skills/playwright-e2e-testing/templates/helpers/ad-gpt-observer.ts +80 -0
  157. package/templates/base/.claude/skills/playwright-e2e-testing/templates/helpers/chromium-mobile-profile.ts +93 -0
  158. package/templates/base/.claude/skills/playwright-e2e-testing/templates/playwright.config.template.js +171 -0
  159. package/templates/base/.claude/skills/playwright-e2e-testing/templates/playwright.config.ts +126 -0
  160. package/templates/base/.claude/skills/playwright-e2e-testing/templates/test-template.spec.js +163 -0
  161. package/templates/base/.claude/skills/playwright-e2e-testing/templates/tests/email-deeplink.ads.spec.ts +44 -0
  162. package/templates/base/.claude/skills/skill-rules.json +184 -0
  163. package/templates/base/.claude/skills/wrangler/SKILL.md +209 -0
  164. package/templates/base/.claude/skills/wrangler/resources/api.md +494 -0
  165. package/templates/base/.claude/skills/wrangler/resources/bundling.md +83 -0
  166. package/templates/base/.claude/skills/wrangler/resources/commands/cert.md +64 -0
  167. package/templates/base/.claude/skills/wrangler/resources/commands/check.md +66 -0
  168. package/templates/base/.claude/skills/wrangler/resources/commands/containers.md +157 -0
  169. package/templates/base/.claude/skills/wrangler/resources/commands/d1.md +843 -0
  170. package/templates/base/.claude/skills/wrangler/resources/commands/delete.md +27 -0
  171. package/templates/base/.claude/skills/wrangler/resources/commands/deploy.md +139 -0
  172. package/templates/base/.claude/skills/wrangler/resources/commands/deployments.md +56 -0
  173. package/templates/base/.claude/skills/wrangler/resources/commands/dev.md +157 -0
  174. package/templates/base/.claude/skills/wrangler/resources/commands/dispatch-namespace.md +69 -0
  175. package/templates/base/.claude/skills/wrangler/resources/commands/docs.md +61 -0
  176. package/templates/base/.claude/skills/wrangler/resources/commands/how-to-run.md +62 -0
  177. package/templates/base/.claude/skills/wrangler/resources/commands/hyperdrive.md +425 -0
  178. package/templates/base/.claude/skills/wrangler/resources/commands/init.md +31 -0
  179. package/templates/base/.claude/skills/wrangler/resources/commands/kv-bulk.md +265 -0
  180. package/templates/base/.claude/skills/wrangler/resources/commands/kv-key.md +353 -0
  181. package/templates/base/.claude/skills/wrangler/resources/commands/kv-namespace.md +265 -0
  182. package/templates/base/.claude/skills/wrangler/resources/commands/login.md +23 -0
  183. package/templates/base/.claude/skills/wrangler/resources/commands/logout.md +19 -0
  184. package/templates/base/.claude/skills/wrangler/resources/commands/mtls-certificate.md +69 -0
  185. package/templates/base/.claude/skills/wrangler/resources/commands/pages.md +175 -0
  186. package/templates/base/.claude/skills/wrangler/resources/commands/pipelines.md +76 -0
  187. package/templates/base/.claude/skills/wrangler/resources/commands/queues.md +132 -0
  188. package/templates/base/.claude/skills/wrangler/resources/commands/r2-bucket.md +342 -0
  189. package/templates/base/.claude/skills/wrangler/resources/commands/r2-object.md +267 -0
  190. package/templates/base/.claude/skills/wrangler/resources/commands/r2-sql.md +65 -0
  191. package/templates/base/.claude/skills/wrangler/resources/commands/rollback.md +40 -0
  192. package/templates/base/.claude/skills/wrangler/resources/commands/secret.md +308 -0
  193. package/templates/base/.claude/skills/wrangler/resources/commands/secrets-store-secret.md +100 -0
  194. package/templates/base/.claude/skills/wrangler/resources/commands/secrets-store-store.md +60 -0
  195. package/templates/base/.claude/skills/wrangler/resources/commands/setup.md +67 -0
  196. package/templates/base/.claude/skills/wrangler/resources/commands/tail.md +37 -0
  197. package/templates/base/.claude/skills/wrangler/resources/commands/telemetry.md +64 -0
  198. package/templates/base/.claude/skills/wrangler/resources/commands/triggers.md +39 -0
  199. package/templates/base/.claude/skills/wrangler/resources/commands/types.md +73 -0
  200. package/templates/base/.claude/skills/wrangler/resources/commands/vectorize.md +941 -0
  201. package/templates/base/.claude/skills/wrangler/resources/commands/versions.md +95 -0
  202. package/templates/base/.claude/skills/wrangler/resources/commands/whoami.md +49 -0
  203. package/templates/base/.claude/skills/wrangler/resources/commands/workflows.md +117 -0
  204. package/templates/base/.claude/skills/wrangler/resources/commands.md +138 -0
  205. package/templates/base/.claude/skills/wrangler/resources/configuration.md +2176 -0
  206. package/templates/base/.claude/skills/wrangler/resources/custom-builds.md +55 -0
  207. package/templates/base/.claude/skills/wrangler/resources/deprecations.md +279 -0
  208. package/templates/base/.claude/skills/wrangler/resources/enviroments.md +416 -0
  209. package/templates/base/.claude/skills/wrangler/resources/extract_sections.py +119 -0
  210. package/templates/base/.claude/skills/wrangler/resources/process_content.py +94 -0
  211. package/templates/base/.claude/skills/wrangler/resources/system-enviroments-variables.md +120 -0
  212. package/templates/base/.dev.vars.example +15 -0
  213. package/templates/base/.env.example +29 -0
  214. package/templates/base/.github/workflows/test.yml +127 -0
  215. package/templates/base/.nycrc.json +16 -0
  216. package/templates/base/CLAUDE.md +218 -0
  217. package/templates/base/README.md +670 -0
  218. package/templates/base/auth-setup-error.png +0 -0
  219. package/templates/base/config/drizzle.config.ts +10 -0
  220. package/templates/base/config/eslint.config.js +364 -0
  221. package/templates/base/config/wrangler.json +76 -0
  222. package/templates/base/docs/app_spec.txt +879 -0
  223. package/templates/base/docs/app_spec_template.md +681 -0
  224. package/templates/base/docs/architecture/README.md +8 -0
  225. package/templates/base/docs/architecture/data-requirements.md +109 -0
  226. package/templates/base/docs/architecture/erd.md +91 -0
  227. package/templates/base/docs/features/feature_list.json +3128 -0
  228. package/templates/base/docs/hono-boilerplate-plan.md +1774 -0
  229. package/templates/base/docs/test-coverage-gap-analysis.md +242 -0
  230. package/templates/base/docs/testing.md +188 -0
  231. package/templates/base/index.html +16 -0
  232. package/templates/base/package.json +115 -0
  233. package/templates/base/playwright.config.ts +158 -0
  234. package/templates/base/pnpm-lock.yaml +8175 -0
  235. package/templates/base/scripts/capture-prod-session.ts +250 -0
  236. package/templates/base/scripts/generate-openapi.ts +23 -0
  237. package/templates/base/scripts/init.sh +121 -0
  238. package/templates/base/src/client/__tests__/button.test.tsx +30 -0
  239. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-dashboard-stats-cards-1.png +0 -0
  240. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-quick-action-cards-1.png +0 -0
  241. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-recent-activity-section-1.png +0 -0
  242. 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
  243. 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
  244. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-render-dashboard-when-authenticated-1.png +0 -0
  245. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-show-navigation-sidebar-1.png +0 -0
  246. 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
  247. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-display-Google-OAuth-login-button-1.png +0 -0
  248. 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
  249. 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
  250. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-page-at--login-route-1.png +0 -0
  251. 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
  252. 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
  253. 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
  254. 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
  255. 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
  256. 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
  257. 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
  258. 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
  259. 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
  260. 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
  261. 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
  262. 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
  263. 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
  264. 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
  265. 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
  266. 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
  267. package/templates/base/src/client/__tests__/routes/authenticated-layout.test.tsx +252 -0
  268. package/templates/base/src/client/__tests__/routes/dashboard.test.tsx +136 -0
  269. package/templates/base/src/client/__tests__/routes/error-components.test.tsx +186 -0
  270. package/templates/base/src/client/__tests__/routes/login.test.tsx +112 -0
  271. package/templates/base/src/client/__tests__/routes/navigation.test.tsx +272 -0
  272. package/templates/base/src/client/__tests__/routes/root-layout.test.tsx +65 -0
  273. package/templates/base/src/client/__tests__/setup-browser.ts +15 -0
  274. package/templates/base/src/client/__tests__/setup.ts +32 -0
  275. package/templates/base/src/client/__tests__/test-utils.tsx +135 -0
  276. package/templates/base/src/client/api/.gitkeep +0 -0
  277. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-can-be-collapsed-by-default-1.png +0 -0
  278. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-expands-when-collapsed-and-expand-button-is-clicked-1.png +0 -0
  279. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-handles-logout-button-click-1.png +0 -0
  280. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-handles-navigation-clicks-1.png +0 -0
  281. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-hides-navigation-labels-when-collapsed-1.png +0 -0
  282. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-highlights-active-route-1.png +0 -0
  283. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-renders-sidebar-navigation-items-1.png +0 -0
  284. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-keyboard-shortcut-hint-when-expanded-1.png +0 -0
  285. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-info-when-authenticated-1.png +0 -0
  286. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-initials-in-avatar-fallback-1.png +0 -0
  287. package/templates/base/src/client/components/__tests__/error-boundary.test.tsx +97 -0
  288. package/templates/base/src/client/components/__tests__/sidebar.test.tsx +281 -0
  289. package/templates/base/src/client/components/error-boundary.tsx +68 -0
  290. package/templates/base/src/client/components/icons.tsx +106 -0
  291. package/templates/base/src/client/components/layout/.gitkeep +0 -0
  292. package/templates/base/src/client/components/sidebar.tsx +267 -0
  293. package/templates/base/src/client/components/ui/.gitkeep +0 -0
  294. package/templates/base/src/client/components/ui/__tests__/avatar.test.tsx +308 -0
  295. package/templates/base/src/client/components/ui/__tests__/card.test.tsx +214 -0
  296. package/templates/base/src/client/components/ui/__tests__/dialog.test.tsx +297 -0
  297. package/templates/base/src/client/components/ui/__tests__/error-fallback.test.tsx +145 -0
  298. package/templates/base/src/client/components/ui/__tests__/input.test.tsx +98 -0
  299. package/templates/base/src/client/components/ui/__tests__/loading-skeleton.test.tsx +139 -0
  300. package/templates/base/src/client/components/ui/__tests__/skeleton.test.tsx +44 -0
  301. package/templates/base/src/client/components/ui/__tests__/sonner.test.tsx +28 -0
  302. package/templates/base/src/client/components/ui/__tests__/tabs.test.tsx +233 -0
  303. package/templates/base/src/client/components/ui/avatar.tsx +101 -0
  304. package/templates/base/src/client/components/ui/badge.tsx +46 -0
  305. package/templates/base/src/client/components/ui/button.tsx +72 -0
  306. package/templates/base/src/client/components/ui/card.tsx +86 -0
  307. package/templates/base/src/client/components/ui/dialog.tsx +140 -0
  308. package/templates/base/src/client/components/ui/error-fallback.tsx +179 -0
  309. package/templates/base/src/client/components/ui/form.tsx +172 -0
  310. package/templates/base/src/client/components/ui/input.tsx +24 -0
  311. package/templates/base/src/client/components/ui/label.tsx +22 -0
  312. package/templates/base/src/client/components/ui/loading-skeleton.tsx +154 -0
  313. package/templates/base/src/client/components/ui/separator.tsx +33 -0
  314. package/templates/base/src/client/components/ui/skeleton.tsx +16 -0
  315. package/templates/base/src/client/components/ui/sonner.tsx +29 -0
  316. package/templates/base/src/client/components/ui/tabs.tsx +121 -0
  317. package/templates/base/src/client/hooks/.gitkeep +0 -0
  318. package/templates/base/src/client/hooks/__tests__/use-auth.test.tsx +306 -0
  319. package/templates/base/src/client/hooks/__tests__/use-theme.test.tsx +172 -0
  320. package/templates/base/src/client/hooks/use-auth.ts +53 -0
  321. package/templates/base/src/client/hooks/use-theme.tsx +78 -0
  322. package/templates/base/src/client/index.css +881 -0
  323. package/templates/base/src/client/lib/query-client.ts +11 -0
  324. package/templates/base/src/client/lib/utils.ts +7 -0
  325. package/templates/base/src/client/main.tsx +26 -0
  326. package/templates/base/src/client/routeTree.gen.ts +258 -0
  327. package/templates/base/src/client/router.ts +15 -0
  328. package/templates/base/src/client/routes/$.tsx +77 -0
  329. package/templates/base/src/client/routes/.gitkeep +0 -0
  330. package/templates/base/src/client/routes/__root.tsx +34 -0
  331. 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
  332. 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
  333. 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
  334. 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
  335. 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
  336. 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
  337. package/templates/base/src/client/routes/__tests__/invite.test.tsx +138 -0
  338. 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
  339. 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
  340. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-API-Access-section-1.png +0 -0
  341. 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
  342. 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
  343. 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
  344. 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
  345. 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
  346. 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
  347. 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
  348. 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
  349. 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
  350. 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
  351. 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
  352. 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
  353. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Webhooks-section-1.png +0 -0
  354. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-connected-count-1.png +0 -0
  355. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-page-description-1.png +0 -0
  356. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-search-input-1.png +0 -0
  357. 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
  358. 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
  359. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-search-by-description-1.png +0 -0
  360. 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
  361. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-existing-webhook-1.png +0 -0
  362. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-events-1.png +0 -0
  363. 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
  364. 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
  365. 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
  366. 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
  367. 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
  368. 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
  369. 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
  370. 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
  371. 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
  372. 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
  373. 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
  374. 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
  375. 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
  376. 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
  377. 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
  378. 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
  379. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-page-description-1.png +0 -0
  380. 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
  381. 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
  382. 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
  383. 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
  384. 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
  385. 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
  386. 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
  387. 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
  388. 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
  389. 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
  390. package/templates/base/src/client/routes/_authenticated/__tests__/account.test.tsx +324 -0
  391. package/templates/base/src/client/routes/_authenticated/__tests__/integrations.test.tsx +520 -0
  392. package/templates/base/src/client/routes/_authenticated/__tests__/settings.test.tsx +414 -0
  393. package/templates/base/src/client/routes/_authenticated/__tests__/team.test.tsx +374 -0
  394. package/templates/base/src/client/routes/_authenticated/account.tsx +416 -0
  395. package/templates/base/src/client/routes/_authenticated/dashboard.tsx +151 -0
  396. package/templates/base/src/client/routes/_authenticated/integrations.tsx +553 -0
  397. package/templates/base/src/client/routes/_authenticated/settings.tsx +310 -0
  398. package/templates/base/src/client/routes/_authenticated/team.tsx +395 -0
  399. package/templates/base/src/client/routes/_authenticated.tsx +69 -0
  400. package/templates/base/src/client/routes/index.tsx +155 -0
  401. package/templates/base/src/client/routes/invite.$token.tsx +191 -0
  402. package/templates/base/src/client/routes/login.tsx +92 -0
  403. package/templates/base/src/server/__tests__/fixtures.ts +461 -0
  404. package/templates/base/src/server/__tests__/mocks/__tests__/db.test.ts +239 -0
  405. package/templates/base/src/server/__tests__/mocks/__tests__/kv.test.ts +293 -0
  406. package/templates/base/src/server/__tests__/mocks/__tests__/r2.test.ts +363 -0
  407. package/templates/base/src/server/__tests__/mocks/db.ts +186 -0
  408. package/templates/base/src/server/__tests__/mocks/index.ts +33 -0
  409. package/templates/base/src/server/__tests__/mocks/kv.ts +286 -0
  410. package/templates/base/src/server/__tests__/mocks/r2.ts +397 -0
  411. package/templates/base/src/server/__tests__/setup.ts +281 -0
  412. package/templates/base/src/server/auth/__tests__/guards.test.ts +162 -0
  413. package/templates/base/src/server/auth/guards.ts +92 -0
  414. package/templates/base/src/server/auth/permissions.test.ts +45 -0
  415. package/templates/base/src/server/auth/permissions.ts +139 -0
  416. package/templates/base/src/server/auth/roles.test.ts +169 -0
  417. package/templates/base/src/server/auth/roles.ts +141 -0
  418. package/templates/base/src/server/db/client.ts +12 -0
  419. package/templates/base/src/server/db/schema/accounts.ts +20 -0
  420. package/templates/base/src/server/db/schema/audit-logs.ts +26 -0
  421. package/templates/base/src/server/db/schema/index.ts +7 -0
  422. package/templates/base/src/server/db/schema/invitations.ts +30 -0
  423. package/templates/base/src/server/db/schema/refresh-tokens.ts +22 -0
  424. package/templates/base/src/server/db/schema/user-accounts.ts +25 -0
  425. package/templates/base/src/server/db/schema/users.ts +33 -0
  426. package/templates/base/src/server/db/seed.ts +267 -0
  427. package/templates/base/src/server/env.test.ts +84 -0
  428. package/templates/base/src/server/env.ts +78 -0
  429. package/templates/base/src/server/index.ts +82 -0
  430. package/templates/base/src/server/lib/audit.ts +73 -0
  431. package/templates/base/src/server/lib/audited-db.test.ts +107 -0
  432. package/templates/base/src/server/lib/audited-db.ts +154 -0
  433. package/templates/base/src/server/lib/email.test.ts +116 -0
  434. package/templates/base/src/server/lib/email.ts +82 -0
  435. package/templates/base/src/server/lib/errors.test.ts +49 -0
  436. package/templates/base/src/server/lib/errors.ts +64 -0
  437. package/templates/base/src/server/lib/oauth.test.ts +238 -0
  438. package/templates/base/src/server/lib/oauth.ts +113 -0
  439. package/templates/base/src/server/lib/pagination.test.ts +52 -0
  440. package/templates/base/src/server/lib/pagination.ts +32 -0
  441. package/templates/base/src/server/lib/password.test.ts +151 -0
  442. package/templates/base/src/server/lib/password.ts +151 -0
  443. package/templates/base/src/server/lib/providers.test.ts +105 -0
  444. package/templates/base/src/server/lib/providers.ts +62 -0
  445. package/templates/base/src/server/lib/r2-storage.test.ts +202 -0
  446. package/templates/base/src/server/lib/r2-storage.ts +107 -0
  447. package/templates/base/src/server/lib/schema-helpers.ts +16 -0
  448. package/templates/base/src/server/lib/session.test.ts +758 -0
  449. package/templates/base/src/server/lib/session.ts +267 -0
  450. package/templates/base/src/server/lib/tokens.test.ts +208 -0
  451. package/templates/base/src/server/lib/tokens.ts +65 -0
  452. package/templates/base/src/server/lib/transaction.test.ts +45 -0
  453. package/templates/base/src/server/lib/transaction.ts +24 -0
  454. package/templates/base/src/server/middleware/account.test.ts +201 -0
  455. package/templates/base/src/server/middleware/account.ts +66 -0
  456. package/templates/base/src/server/middleware/auth.test.ts +345 -0
  457. package/templates/base/src/server/middleware/auth.ts +146 -0
  458. package/templates/base/src/server/middleware/cors.test.ts +88 -0
  459. package/templates/base/src/server/middleware/cors.ts +26 -0
  460. package/templates/base/src/server/middleware/error-handler.test.ts +69 -0
  461. package/templates/base/src/server/middleware/error-handler.ts +43 -0
  462. package/templates/base/src/server/middleware/index.ts +8 -0
  463. package/templates/base/src/server/middleware/rate-limit.test.ts +472 -0
  464. package/templates/base/src/server/middleware/rate-limit.ts +294 -0
  465. package/templates/base/src/server/middleware/request-context.test.ts +175 -0
  466. package/templates/base/src/server/middleware/request-context.ts +28 -0
  467. package/templates/base/src/server/middleware/request-logger.test.ts +92 -0
  468. package/templates/base/src/server/middleware/request-logger.ts +50 -0
  469. package/templates/base/src/server/routes/accounts/__tests__/handlers.test.ts +460 -0
  470. package/templates/base/src/server/routes/accounts/handlers.ts +179 -0
  471. package/templates/base/src/server/routes/accounts/index.ts +55 -0
  472. package/templates/base/src/server/routes/accounts/routes.ts +205 -0
  473. package/templates/base/src/server/routes/accounts/schemas.ts +31 -0
  474. package/templates/base/src/server/routes/api.ts +37 -0
  475. package/templates/base/src/server/routes/audits/__tests__/handlers.test.ts +349 -0
  476. package/templates/base/src/server/routes/audits/handlers.ts +37 -0
  477. package/templates/base/src/server/routes/audits/index.ts +14 -0
  478. package/templates/base/src/server/routes/audits/routes.ts +29 -0
  479. package/templates/base/src/server/routes/audits/schemas.ts +43 -0
  480. package/templates/base/src/server/routes/auth/__tests__/handlers.test.ts +381 -0
  481. package/templates/base/src/server/routes/auth/handlers.ts +222 -0
  482. package/templates/base/src/server/routes/auth/index.ts +37 -0
  483. package/templates/base/src/server/routes/auth/routes.ts +136 -0
  484. package/templates/base/src/server/routes/auth/schemas.ts +48 -0
  485. package/templates/base/src/server/routes/auth/test-login.ts +156 -0
  486. package/templates/base/src/server/routes/health/__tests__/handlers.test.ts +237 -0
  487. package/templates/base/src/server/routes/health/handlers.ts +83 -0
  488. package/templates/base/src/server/routes/health/index.ts +13 -0
  489. package/templates/base/src/server/routes/health/routes.ts +90 -0
  490. package/templates/base/src/server/routes/index.ts +53 -0
  491. package/templates/base/src/server/routes/invitations/__tests__/handlers.test.ts +473 -0
  492. package/templates/base/src/server/routes/invitations/handlers.ts +71 -0
  493. package/templates/base/src/server/routes/invitations/index.ts +25 -0
  494. package/templates/base/src/server/routes/invitations/routes.ts +69 -0
  495. package/templates/base/src/server/routes/invitations/schemas.ts +39 -0
  496. package/templates/base/src/server/routes/openapi.ts +14 -0
  497. package/templates/base/src/server/routes/schemas.ts +53 -0
  498. package/templates/base/src/server/routes/storage/__tests__/handlers.test.ts +408 -0
  499. package/templates/base/src/server/routes/storage/handlers.ts +100 -0
  500. package/templates/base/src/server/routes/storage/index.ts +42 -0
  501. package/templates/base/src/server/routes/storage/routes.ts +91 -0
  502. package/templates/base/src/server/routes/storage/schemas.ts +56 -0
  503. package/templates/base/src/server/routes/users/__tests__/handlers.test.ts +526 -0
  504. package/templates/base/src/server/routes/users/handlers.ts +228 -0
  505. package/templates/base/src/server/routes/users/index.ts +67 -0
  506. package/templates/base/src/server/routes/users/routes.ts +265 -0
  507. package/templates/base/src/server/routes/users/schemas.ts +67 -0
  508. package/templates/base/src/server/services/__tests__/accounts.test.ts +764 -0
  509. package/templates/base/src/server/services/__tests__/audits.test.ts +235 -0
  510. package/templates/base/src/server/services/__tests__/auth.test.ts +765 -0
  511. package/templates/base/src/server/services/__tests__/invitations.test.ts +704 -0
  512. package/templates/base/src/server/services/__tests__/users.test.ts +755 -0
  513. package/templates/base/src/server/services/accounts.ts +269 -0
  514. package/templates/base/src/server/services/audits.ts +82 -0
  515. package/templates/base/src/server/services/auth.ts +225 -0
  516. package/templates/base/src/server/services/index.ts +6 -0
  517. package/templates/base/src/server/services/invitations.ts +306 -0
  518. package/templates/base/src/server/services/users.ts +350 -0
  519. package/templates/base/src/server/types/auth.ts +36 -0
  520. package/templates/base/src/server/types/index.ts +117 -0
  521. package/templates/base/src/shared/schemas/.gitkeep +0 -0
  522. package/templates/base/src/shared/schemas/__tests__/schemas.test.ts +547 -0
  523. package/templates/base/src/shared/schemas/account.ts +15 -0
  524. package/templates/base/src/shared/schemas/index.ts +6 -0
  525. package/templates/base/src/shared/schemas/invitation.ts +9 -0
  526. package/templates/base/src/shared/schemas/profile.ts +10 -0
  527. package/templates/base/src/shared/schemas/user.ts +16 -0
  528. package/templates/base/src/shared/schemas/webhook.ts +12 -0
  529. package/templates/base/src/shared/types/.gitkeep +0 -0
  530. package/templates/base/src/shared/types/account.ts +12 -0
  531. package/templates/base/src/shared/types/api.ts +1399 -0
  532. package/templates/base/src/shared/types/auth.ts +24 -0
  533. package/templates/base/src/shared/types/index.ts +5 -0
  534. package/templates/base/src/shared/types/user.ts +31 -0
  535. package/templates/base/src/test/vitest-zod-matcher.ts +37 -0
  536. package/templates/base/src/test/vitest.d.ts +19 -0
  537. package/templates/base/tests/e2e/README.md +141 -0
  538. package/templates/base/tests/e2e/a11y/accessibility.spec.ts +925 -0
  539. package/templates/base/tests/e2e/a11y/keyboard-navigation.spec.ts +610 -0
  540. package/templates/base/tests/e2e/api/accounts.spec.ts +148 -0
  541. package/templates/base/tests/e2e/api/audit-logs.spec.ts +130 -0
  542. package/templates/base/tests/e2e/api/authenticated-api.spec.ts +311 -0
  543. package/templates/base/tests/e2e/api/storage.spec.ts +109 -0
  544. package/templates/base/tests/e2e/auth-flows.unauth.spec.ts +117 -0
  545. package/templates/base/tests/e2e/auth-logout.unauth.spec.ts +103 -0
  546. package/templates/base/tests/e2e/auth.setup.ts +115 -0
  547. package/templates/base/tests/e2e/auth.spec.ts +146 -0
  548. package/templates/base/tests/e2e/compatibility/cross-browser.spec.ts +152 -0
  549. package/templates/base/tests/e2e/compatibility/cross-browser.spec.ts-snapshots/login-chromium-chromium-darwin.png +0 -0
  550. package/templates/base/tests/e2e/crud/account.spec.ts +356 -0
  551. package/templates/base/tests/e2e/crud/integrations.spec.ts +419 -0
  552. package/templates/base/tests/e2e/crud/team.spec.ts +287 -0
  553. package/templates/base/tests/e2e/crud/users.spec.ts +239 -0
  554. package/templates/base/tests/e2e/errors/error-boundary.spec.ts +428 -0
  555. package/templates/base/tests/e2e/errors/error-handling.spec.ts +47 -0
  556. package/templates/base/tests/e2e/errors/error-handling.unauth.spec.ts +205 -0
  557. package/templates/base/tests/e2e/fixtures.ts +266 -0
  558. package/templates/base/tests/e2e/forms/validation.spec.ts +569 -0
  559. package/templates/base/tests/e2e/invitations/invite-flow.unauth.spec.ts +204 -0
  560. package/templates/base/tests/e2e/journeys/account-lifecycle.spec.ts +314 -0
  561. package/templates/base/tests/e2e/journeys/audit-investigation.spec.ts +299 -0
  562. package/templates/base/tests/e2e/journeys/auth-onboarding.spec.ts +232 -0
  563. package/templates/base/tests/e2e/journeys/critical-flows.spec.ts +281 -0
  564. package/templates/base/tests/e2e/journeys/error-recovery.spec.ts +354 -0
  565. package/templates/base/tests/e2e/journeys/file-management.spec.ts +307 -0
  566. package/templates/base/tests/e2e/journeys/integrations.spec.ts +372 -0
  567. package/templates/base/tests/e2e/journeys/multi-account.spec.ts +317 -0
  568. package/templates/base/tests/e2e/journeys/rbac-enforcement.spec.ts +389 -0
  569. package/templates/base/tests/e2e/journeys/settings-profile.spec.ts +400 -0
  570. package/templates/base/tests/e2e/journeys/team-collaboration.spec.ts +410 -0
  571. package/templates/base/tests/e2e/mobile/responsive.spec.ts +178 -0
  572. package/templates/base/tests/e2e/navigation/routing.spec.ts +371 -0
  573. package/templates/base/tests/e2e/navigation/sidebar.spec.ts +425 -0
  574. package/templates/base/tests/e2e/pages/ui-features.spec.ts +393 -0
  575. package/templates/base/tests/e2e/performance/baselines.spec.ts +162 -0
  576. package/templates/base/tests/e2e/performance/benchmarks.spec.ts +371 -0
  577. package/templates/base/tests/e2e/smoke.unauth.spec.ts +196 -0
  578. package/templates/base/tests/e2e/visual/components.spec.ts +650 -0
  579. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/confirmation-dialog-visual-darwin.png +0 -0
  580. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-background-visual-darwin.png +0 -0
  581. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-card-visual-darwin.png +0 -0
  582. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-sidebar-visual-darwin.png +0 -0
  583. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dashboard-card-single-visual-darwin.png +0 -0
  584. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dialog-content-visual-darwin.png +0 -0
  585. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dialog-with-backdrop-visual-darwin.png +0 -0
  586. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/empty-search-results-visual-darwin.png +0 -0
  587. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/input-default-visual-darwin.png +0 -0
  588. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/input-focus-ring-visual-darwin.png +0 -0
  589. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-focus-visual-darwin.png +0 -0
  590. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-hover-visual-darwin.png +0 -0
  591. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-visual-darwin.png +0 -0
  592. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-active-nav-item-visual-darwin.png +0 -0
  593. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-collapsed-visual-darwin.png +0 -0
  594. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-navigation-visual-darwin.png +0 -0
  595. package/templates/base/tests/e2e/visual/core-components.spec.ts +192 -0
  596. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/account-page-visual-darwin.png +0 -0
  597. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/create-webhook-dialog-visual-darwin.png +0 -0
  598. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/dashboard-page-visual-darwin.png +0 -0
  599. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/delete-account-dialog-visual-darwin.png +0 -0
  600. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/integrations-page-visual-darwin.png +0 -0
  601. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/invite-member-dialog-visual-darwin.png +0 -0
  602. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/login-page-visual-darwin.png +0 -0
  603. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/settings-account-tab-visual-darwin.png +0 -0
  604. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/settings-profile-tab-visual-darwin.png +0 -0
  605. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/sidebar-navigation-visual-darwin.png +0 -0
  606. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/team-page-visual-darwin.png +0 -0
  607. package/templates/base/tests/e2e/visual/screenshots.spec.ts +230 -0
  608. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/404-page-chromium-darwin.png +0 -0
  609. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/404-page-visual-darwin.png +0 -0
  610. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/account-page-visual-darwin.png +0 -0
  611. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/login-page-chromium-darwin.png +0 -0
  612. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/login-page-visual-darwin.png +0 -0
  613. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/settings-page-visual-darwin.png +0 -0
  614. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/team-invite-dialog-visual-darwin.png +0 -0
  615. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/team-page-visual-darwin.png +0 -0
  616. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/webhook-create-dialog-visual-darwin.png +0 -0
  617. package/templates/base/tests/e2e/visual/theme-colors.spec.ts +293 -0
  618. package/templates/base/tests/e2e/visual/ui-components.spec.ts +502 -0
  619. package/templates/base/tests/integration/accounts/crud.test.ts +1402 -0
  620. package/templates/base/tests/integration/audits/list.test.ts +1133 -0
  621. package/templates/base/tests/integration/auth/auth-service.test.ts +415 -0
  622. package/templates/base/tests/integration/auth/invitation-token.test.ts +529 -0
  623. package/templates/base/tests/integration/auth/logout.test.ts +524 -0
  624. package/templates/base/tests/integration/auth/oauth.test.ts +768 -0
  625. package/templates/base/tests/integration/auth/refresh-token.test.ts +364 -0
  626. package/templates/base/tests/integration/auth/session-expiry.test.ts +569 -0
  627. package/templates/base/tests/integration/auth/session.test.ts +520 -0
  628. package/templates/base/tests/integration/auth/super-admin.test.ts +451 -0
  629. package/templates/base/tests/integration/authorization/analytics-role.test.ts +1026 -0
  630. package/templates/base/tests/integration/authorization/billing-role.test.ts +776 -0
  631. package/templates/base/tests/integration/authorization/guards-roles.test.ts +539 -0
  632. package/templates/base/tests/integration/authorization/multi-tenancy.test.ts +1112 -0
  633. package/templates/base/tests/integration/authorization/role-hierarchy.test.ts +931 -0
  634. package/templates/base/tests/integration/authorization/roles.test.ts +1347 -0
  635. package/templates/base/tests/integration/config/production-behavior.test.ts +536 -0
  636. package/templates/base/tests/integration/db/constraints.test.ts +695 -0
  637. package/templates/base/tests/integration/fixtures/accounts.ts +136 -0
  638. package/templates/base/tests/integration/fixtures/index.ts +232 -0
  639. package/templates/base/tests/integration/fixtures/invitations.ts +195 -0
  640. package/templates/base/tests/integration/fixtures/users.ts +144 -0
  641. package/templates/base/tests/integration/health/health.test.ts +351 -0
  642. package/templates/base/tests/integration/invitations/crud.test.ts +1457 -0
  643. package/templates/base/tests/integration/invitations/email.test.ts +506 -0
  644. package/templates/base/tests/integration/lib/email.test.ts +174 -0
  645. package/templates/base/tests/integration/lib/oauth.test.ts +368 -0
  646. package/templates/base/tests/integration/lib/password.test.ts +192 -0
  647. package/templates/base/tests/integration/lib/schema-helpers.test.ts +129 -0
  648. package/templates/base/tests/integration/lib/tokens.test.ts +304 -0
  649. package/templates/base/tests/integration/middleware/auth.test.ts +499 -0
  650. package/templates/base/tests/integration/middleware/cors.test.ts +334 -0
  651. package/templates/base/tests/integration/middleware/request-context.test.ts +156 -0
  652. package/templates/base/tests/integration/middleware/request-logger.test.ts +313 -0
  653. package/templates/base/tests/integration/performance/response-times.test.ts +509 -0
  654. package/templates/base/tests/integration/security/cookie-security.test.ts +567 -0
  655. package/templates/base/tests/integration/security/csrf-protection.test.ts +542 -0
  656. package/templates/base/tests/integration/security/jwt-validation.test.ts +209 -0
  657. package/templates/base/tests/integration/security/log-sanitization.test.ts +658 -0
  658. package/templates/base/tests/integration/security/rate-limiting.test.ts +1251 -0
  659. package/templates/base/tests/integration/security/sql-injection.test.ts +663 -0
  660. package/templates/base/tests/integration/security/token-hashing.test.ts +371 -0
  661. package/templates/base/tests/integration/security/xss-prevention.test.ts +541 -0
  662. package/templates/base/tests/integration/setup.ts +834 -0
  663. package/templates/base/tests/integration/smoke.test.ts +288 -0
  664. package/templates/base/tests/integration/storage/upload.test.ts +1162 -0
  665. package/templates/base/tests/integration/storage/validation.test.ts +746 -0
  666. package/templates/base/tests/integration/users/crud.test.ts +1297 -0
  667. package/templates/base/tests/integration/users/list.test.ts +698 -0
  668. package/templates/base/tests/integration/vitest.config.ts +80 -0
  669. package/templates/base/tsconfig.app.json +18 -0
  670. package/templates/base/tsconfig.json +39 -0
  671. package/templates/base/tsconfig.node.json +16 -0
  672. package/templates/base/tsconfig.server.json +26 -0
  673. package/templates/base/tsconfig.tsbuildinfo +1 -0
  674. package/templates/base/vite.config.ts +46 -0
  675. package/templates/base/vitest.config.browser.ts +47 -0
  676. package/templates/base/vitest.config.frontend.ts +47 -0
  677. package/templates/base/vitest.config.ts +82 -0
  678. package/templates/base/vitest.workspace.ts +22 -0
  679. package/templates/modules/audit-logs/.gitkeep +0 -0
  680. package/templates/modules/billing/.gitkeep +0 -0
  681. package/templates/modules/invitations/.gitkeep +0 -0
  682. package/templates/modules/storage/.gitkeep +0 -0
  683. package/templates/modules/webhooks/.gitkeep +0 -0
  684. package/templates/providers/auth-email/.gitkeep +0 -0
  685. package/templates/providers/auth-github/.gitkeep +0 -0
  686. package/templates/providers/auth-google/.gitkeep +0 -0
  687. package/templates/providers/email-resend/.gitkeep +0 -0
  688. package/templates/providers/email-sendgrid/.gitkeep +0 -0
@@ -0,0 +1,925 @@
1
+ import { test, expect, isAuthenticated } from '../fixtures'
2
+
3
+ /**
4
+ * Accessibility E2E Tests
5
+ *
6
+ * Tests for accessibility features including keyboard navigation,
7
+ * focus management, semantic structure, and ARIA attributes.
8
+ *
9
+ * Uses Playwright's built-in accessibility testing features:
10
+ * - toHaveAccessibleName() for accessible names
11
+ * - toHaveRole() for ARIA roles
12
+ * - toHaveAccessibleDescription() for descriptions
13
+ *
14
+ * @tags @a11y
15
+ */
16
+
17
+ test.describe('Accessibility Tests @a11y', () => {
18
+ test.describe('Public Pages', () => {
19
+ test('login page has proper heading structure', async ({ page }) => {
20
+ await page.goto('/login')
21
+
22
+ // Should have a heading (Welcome back) - CardTitle renders as h3
23
+ const heading = page.getByRole('heading', { name: /welcome back/i })
24
+ await expect(heading).toBeVisible()
25
+
26
+ // Verify heading has semantic meaning (is an actual heading element)
27
+ const tagName = await heading.evaluate((el) => el.tagName.toLowerCase())
28
+ expect(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']).toContain(tagName)
29
+ })
30
+
31
+ test('login page form is accessible (OAuth button focusable)', async ({ page }) => {
32
+ await page.goto('/login')
33
+
34
+ // OAuth button should be focusable
35
+ const googleButton = page.getByRole('button', { name: /continue with google/i })
36
+ await expect(googleButton).toBeVisible()
37
+ await expect(googleButton).toBeEnabled()
38
+
39
+ // Focus the button
40
+ await googleButton.focus()
41
+ await expect(googleButton).toBeFocused()
42
+
43
+ // Button should have accessible name
44
+ await expect(googleButton).toHaveAccessibleName(/continue with google/i)
45
+ })
46
+
47
+ test('login page supports keyboard navigation', async ({ page }) => {
48
+ await page.goto('/login')
49
+
50
+ // Verify the OAuth button is keyboard accessible
51
+ const googleButton = page.getByRole('button', { name: /continue with google/i })
52
+ await expect(googleButton).toBeVisible()
53
+
54
+ // Focus the button directly to verify it can receive focus
55
+ await googleButton.focus()
56
+ await expect(googleButton).toBeFocused()
57
+
58
+ // Tab to next element and verify focus moves
59
+ await page.keyboard.press('Tab')
60
+ const activeElement = page.locator(':focus')
61
+ await expect(activeElement).toBeVisible()
62
+
63
+ // Verify we can navigate back with Shift+Tab
64
+ await page.keyboard.press('Shift+Tab')
65
+ await expect(googleButton).toBeFocused()
66
+ })
67
+
68
+ test('login page links have proper focus indicators', async ({ page }) => {
69
+ await page.goto('/login')
70
+
71
+ // Navigate to a link
72
+ const termsLink = page.getByRole('link', { name: /terms of service/i })
73
+ await termsLink.focus()
74
+ await expect(termsLink).toBeFocused()
75
+
76
+ // Link should have underline for visibility
77
+ await expect(termsLink).toHaveClass(/underline/)
78
+ })
79
+ })
80
+
81
+ test.describe('Authenticated Pages', () => {
82
+ test.beforeEach(async ({ page }) => {
83
+ const authenticated = await isAuthenticated(page)
84
+ test.skip(!authenticated, 'No authenticated session available')
85
+ })
86
+
87
+ test('dashboard has proper semantic structure', async ({ page }) => {
88
+ await page.goto('/dashboard')
89
+
90
+ // Should have main heading
91
+ const heading = page.getByRole('heading', { name: /dashboard/i })
92
+ await expect(heading).toBeVisible()
93
+
94
+ // Page should have a main content area (div with proper structure)
95
+ const mainContent = page.locator('main, [role="main"], .space-y-6').first()
96
+ await expect(mainContent).toBeVisible()
97
+ })
98
+
99
+ test('team page table/list is accessible', async ({ page }) => {
100
+ await page.goto('/team')
101
+
102
+ // Should have heading
103
+ await expect(page.getByRole('heading', { name: /team members/i })).toBeVisible()
104
+
105
+ // Team members should be in a structured list (divide-y creates visual separation)
106
+ const memberList = page.locator('[class*="divide-y"]').first()
107
+ await expect(memberList).toBeVisible()
108
+
109
+ // Each member row should be visible and contain content
110
+ const firstMemberRow = memberList.locator('> div').first()
111
+ await expect(firstMemberRow).toBeVisible()
112
+ })
113
+
114
+ test('settings tabs are keyboard accessible', async ({ page }) => {
115
+ await page.goto('/settings')
116
+
117
+ // Verify tabs are present
118
+ const tabList = page.getByRole('tablist')
119
+ await expect(tabList).toBeVisible()
120
+
121
+ // Profile tab should be selected by default
122
+ const profileTab = page.getByRole('tab', { name: /profile/i })
123
+ await expect(profileTab).toHaveAttribute('data-state', 'active')
124
+
125
+ // Focus on tab list
126
+ await profileTab.focus()
127
+ await expect(profileTab).toBeFocused()
128
+
129
+ // Arrow right should move to next tab (Account)
130
+ await page.keyboard.press('ArrowRight')
131
+ const accountTab = page.getByRole('tab', { name: /account/i })
132
+ await expect(accountTab).toBeFocused()
133
+
134
+ // Arrow right again to Notifications
135
+ await page.keyboard.press('ArrowRight')
136
+ const notificationsTab = page.getByRole('tab', { name: /notifications/i })
137
+ await expect(notificationsTab).toBeFocused()
138
+
139
+ // Arrow left should go back
140
+ await page.keyboard.press('ArrowLeft')
141
+ await expect(accountTab).toBeFocused()
142
+
143
+ // Enter should activate the tab
144
+ await page.keyboard.press('Enter')
145
+ await expect(accountTab).toHaveAttribute('data-state', 'active')
146
+
147
+ // Tab content should be visible
148
+ await expect(page.getByText(/connected accounts/i)).toBeVisible()
149
+ })
150
+
151
+ test('team invite dialog is accessible (focus trap, escape to close)', async ({ page }) => {
152
+ await page.goto('/team')
153
+
154
+ // Open invite dialog
155
+ const inviteButton = page.getByRole('button', { name: /invite member/i })
156
+ await inviteButton.click()
157
+
158
+ // Dialog should be visible
159
+ const dialog = page.getByRole('dialog')
160
+ await expect(dialog).toBeVisible()
161
+
162
+ // Dialog should have proper title
163
+ await expect(page.getByRole('heading', { name: /invite team member/i })).toBeVisible()
164
+
165
+ // First focusable element in dialog should receive focus (email input)
166
+ const emailInput = page.getByLabel(/email address/i)
167
+ await expect(emailInput).toBeFocused()
168
+
169
+ // Tab through dialog elements - focus should stay trapped
170
+ await page.keyboard.press('Tab') // Move to role buttons
171
+
172
+ // Tab to cancel button
173
+ let tabCount = 0
174
+ const maxTabs = 10
175
+ while (tabCount < maxTabs) {
176
+ await page.keyboard.press('Tab')
177
+ tabCount++
178
+ const cancelButton = page.getByRole('button', { name: /cancel/i })
179
+ if (await cancelButton.evaluate((el) => el === document.activeElement)) {
180
+ break
181
+ }
182
+ }
183
+
184
+ // Escape should close dialog
185
+ await page.keyboard.press('Escape')
186
+ await expect(dialog).not.toBeVisible()
187
+ })
188
+
189
+ test('form inputs have labels', async ({ page }) => {
190
+ await page.goto('/settings')
191
+
192
+ // Full Name input should have label
193
+ const nameInput = page.getByLabel(/full name/i)
194
+ await expect(nameInput).toBeVisible()
195
+ await expect(nameInput).toHaveAttribute('id', 'name')
196
+
197
+ // Email input should have label
198
+ const emailInput = page.getByLabel(/email address/i)
199
+ await expect(emailInput).toBeVisible()
200
+ await expect(emailInput).toHaveAttribute('id', 'email')
201
+ })
202
+
203
+ test('buttons have accessible names', async ({ page }) => {
204
+ await page.goto('/settings')
205
+
206
+ // Save Changes button
207
+ const saveButton = page.getByRole('button', { name: /save changes/i })
208
+ await expect(saveButton).toBeVisible()
209
+ await expect(saveButton).toHaveAccessibleName(/save changes/i)
210
+
211
+ // Change Photo button
212
+ const photoButton = page.getByRole('button', { name: /change photo/i })
213
+ await expect(photoButton).toBeVisible()
214
+ await expect(photoButton).toHaveAccessibleName(/change photo/i)
215
+
216
+ // Go to Account tab for Delete button
217
+ await page.getByRole('tab', { name: /account/i }).click()
218
+ const deleteButton = page.getByRole('button', { name: /delete account/i })
219
+ await expect(deleteButton).toBeVisible()
220
+ await expect(deleteButton).toHaveAccessibleName(/delete account/i)
221
+ })
222
+
223
+ test('navigation is keyboard accessible', async ({ page }) => {
224
+ await page.goto('/dashboard')
225
+
226
+ // Find navigation links
227
+ const navLinks = page.locator('nav').getByRole('link')
228
+ const linkCount = await navLinks.count()
229
+
230
+ if (linkCount > 0) {
231
+ // Focus first nav link
232
+ await navLinks.first().focus()
233
+ await expect(navLinks.first()).toBeFocused()
234
+
235
+ // Should be able to tab through nav links
236
+ for (let i = 1; i < Math.min(linkCount, 3); i++) {
237
+ await page.keyboard.press('Tab')
238
+ }
239
+
240
+ // Enter should activate the link (navigate)
241
+ const currentUrl = page.url()
242
+ await navLinks.first().focus()
243
+ await page.keyboard.press('Enter')
244
+ // URL should change or page should respond
245
+ await page.waitForLoadState('domcontentloaded')
246
+ }
247
+ })
248
+
249
+ test('color contrast check (basic - text is visible)', async ({ page }) => {
250
+ await page.goto('/settings')
251
+
252
+ // Verify main heading text is visible and has proper styling
253
+ const heading = page.getByRole('heading', { name: /settings/i })
254
+ await expect(heading).toBeVisible()
255
+
256
+ // Verify description text is visible (muted but readable)
257
+ const description = page.getByText(/manage your account settings/i)
258
+ await expect(description).toBeVisible()
259
+
260
+ // Verify form labels are visible
261
+ const nameLabel = page.getByText('Full Name')
262
+ await expect(nameLabel).toBeVisible()
263
+
264
+ // Verify input text is visible
265
+ const nameInput = page.getByLabel(/full name/i)
266
+ await expect(nameInput).toBeVisible()
267
+
268
+ // Verify button text is visible and has sufficient contrast
269
+ const saveButton = page.getByRole('button', { name: /save changes/i })
270
+ await expect(saveButton).toBeVisible()
271
+
272
+ // Verify disabled input has visible styling
273
+ const emailInput = page.getByLabel(/email address/i)
274
+ await expect(emailInput).toBeVisible()
275
+ await expect(emailInput).toBeDisabled()
276
+
277
+ // Navigate to Account tab to check destructive button contrast
278
+ await page.getByRole('tab', { name: /account/i }).click()
279
+ const deleteButton = page.getByRole('button', { name: /delete account/i })
280
+ await expect(deleteButton).toBeVisible()
281
+ })
282
+
283
+ test('team page search input is accessible', async ({ page }) => {
284
+ await page.goto('/team')
285
+
286
+ // Search input should have placeholder as accessible description
287
+ const searchInput = page.getByPlaceholder(/search members/i)
288
+ await expect(searchInput).toBeVisible()
289
+ await expect(searchInput).toBeEnabled()
290
+
291
+ // Should be focusable
292
+ await searchInput.focus()
293
+ await expect(searchInput).toBeFocused()
294
+
295
+ // Should accept keyboard input
296
+ await page.keyboard.type('test')
297
+ await expect(searchInput).toHaveValue('test')
298
+ })
299
+
300
+ test('settings notification toggles are accessible', async ({ page }) => {
301
+ await page.goto('/settings')
302
+
303
+ // Navigate to notifications tab
304
+ await page.getByRole('tab', { name: /notifications/i }).click()
305
+
306
+ // Should see switch elements
307
+ const switches = page.getByRole('switch')
308
+ const switchCount = await switches.count()
309
+
310
+ expect(switchCount).toBeGreaterThan(0)
311
+
312
+ // First switch should be focusable
313
+ const firstSwitch = switches.first()
314
+ await firstSwitch.focus()
315
+ await expect(firstSwitch).toBeFocused()
316
+
317
+ // Should have aria-checked attribute
318
+ await expect(firstSwitch).toHaveAttribute('aria-checked')
319
+
320
+ // Should be keyboard toggleable with Space/Enter
321
+ const initialState = await firstSwitch.getAttribute('aria-checked')
322
+
323
+ // Skip if disabled
324
+ const isDisabled = await firstSwitch.isDisabled()
325
+ if (!isDisabled) {
326
+ await page.keyboard.press('Space')
327
+ const newState = await firstSwitch.getAttribute('aria-checked')
328
+ expect(newState).not.toBe(initialState)
329
+ }
330
+ })
331
+
332
+ test('avatar images have alt text', async ({ page }) => {
333
+ await page.goto('/settings')
334
+
335
+ // Avatar should have alt text
336
+ const avatarImage = page.locator('[class*="Avatar"] img')
337
+ const imageCount = await avatarImage.count()
338
+
339
+ if (imageCount > 0) {
340
+ await expect(avatarImage.first()).toHaveAttribute('alt')
341
+ } else {
342
+ // Fallback avatar should still be accessible
343
+ const avatarFallback = page.locator('[class*="AvatarFallback"]')
344
+ await expect(avatarFallback).toBeVisible()
345
+ }
346
+ })
347
+
348
+ test('skip to main content functionality', async ({ page }) => {
349
+ await page.goto('/dashboard')
350
+
351
+ // Many accessible sites have a skip link that appears on focus
352
+ // Check if one exists or if the page structure allows quick navigation
353
+ await page.keyboard.press('Tab')
354
+
355
+ // The first focusable element should allow navigation
356
+ // In most cases, this would be a skip link or the main navigation
357
+ const focusedElement = page.locator(':focus')
358
+ await expect(focusedElement).toBeVisible()
359
+ })
360
+
361
+ test('dialogs trap focus correctly', async ({ page }) => {
362
+ await page.goto('/team')
363
+
364
+ // Open invite dialog
365
+ await page.getByRole('button', { name: /invite member/i }).click()
366
+ const dialog = page.getByRole('dialog')
367
+ await expect(dialog).toBeVisible()
368
+
369
+ // Tab through all elements in the dialog
370
+ const tabPresses = 15 // More than enough to cycle through dialog
371
+ for (let i = 0; i < tabPresses; i++) {
372
+ await page.keyboard.press('Tab')
373
+ }
374
+
375
+ // Focus should still be within the dialog
376
+ const focusedElement = page.locator(':focus')
377
+ const isInDialog = await focusedElement.evaluate((el) => {
378
+ const dialog = el.closest('[role="dialog"]')
379
+ return dialog !== null
380
+ })
381
+
382
+ expect(isInDialog).toBe(true)
383
+
384
+ // Close dialog
385
+ await page.keyboard.press('Escape')
386
+ await expect(dialog).not.toBeVisible()
387
+ })
388
+
389
+ test('error states are accessible', async ({ page }) => {
390
+ await page.goto('/team')
391
+
392
+ // Open invite dialog
393
+ await page.getByRole('button', { name: /invite member/i }).click()
394
+
395
+ // Send button should be disabled when email is empty
396
+ const sendButton = page.getByRole('button', { name: /send invitation/i })
397
+ await expect(sendButton).toBeDisabled()
398
+
399
+ // Enter invalid email
400
+ const emailInput = page.getByLabel(/email address/i)
401
+ await emailInput.fill('invalid-email')
402
+
403
+ // Button state should reflect validity
404
+ // Most implementations either show inline error or disable button
405
+ await expect(sendButton).toBeVisible()
406
+
407
+ // Close dialog
408
+ await page.keyboard.press('Escape')
409
+ })
410
+
411
+ test('account page has proper headings and landmarks', async ({ page }) => {
412
+ await page.goto('/account')
413
+
414
+ // Should have main heading (h1)
415
+ const mainHeading = page.getByRole('heading', { level: 1 })
416
+ await expect(mainHeading).toBeVisible()
417
+
418
+ // Should have multiple section headings (h2) for different account sections
419
+ // Account page typically has: Security, Sessions, API Access, Danger Zone
420
+ const sectionHeadings = page.getByRole('heading', { level: 2 })
421
+ const headingCount = await sectionHeadings.count()
422
+ expect(headingCount).toBeGreaterThanOrEqual(3)
423
+ })
424
+
425
+ test('dialogs have proper ARIA attributes', async ({ page }) => {
426
+ await page.goto('/team')
427
+
428
+ // Wait for the page heading to ensure content is loaded
429
+ const pageHeading = page.getByRole('heading', { level: 1, name: /team members/i })
430
+ await expect(pageHeading).toBeVisible()
431
+
432
+ // Open invite dialog - click the second button (the visible styled one)
433
+ const inviteButton = page.getByText('Invite Member', { exact: true })
434
+ await expect(inviteButton).toBeVisible()
435
+ await inviteButton.click()
436
+
437
+ // Dialog should be visible
438
+ const dialog = page.getByRole('dialog')
439
+ await expect(dialog).toBeVisible()
440
+
441
+ // Dialog should have proper ARIA attributes for accessibility
442
+ const hasAriaLabelledBy = await dialog.getAttribute('aria-labelledby')
443
+ const hasAriaLabel = await dialog.getAttribute('aria-label')
444
+ const hasAriaModal = await dialog.getAttribute('aria-modal')
445
+
446
+ // At least one labeling attribute or aria-modal should be present
447
+ const hasProperAriaAttributes =
448
+ hasAriaLabelledBy !== null || hasAriaLabel !== null || hasAriaModal !== null
449
+ expect(hasProperAriaAttributes).toBe(true)
450
+
451
+ // Close dialog
452
+ await page.keyboard.press('Escape')
453
+ await expect(dialog).not.toBeVisible()
454
+ })
455
+
456
+ test('form inputs have associated labels via getByLabel', async ({ page }) => {
457
+ await page.goto('/settings')
458
+
459
+ // Wait for the settings page heading to ensure page is loaded
460
+ const heading = page.getByRole('heading', { level: 1, name: /settings/i })
461
+ await expect(heading).toBeVisible()
462
+
463
+ // Name input should be accessible via label
464
+ const nameInput = page.getByLabel(/full name/i)
465
+ await expect(nameInput).toBeVisible()
466
+ await expect(nameInput).toBeEnabled()
467
+
468
+ // Email input should be accessible via label (but may be disabled for OAuth users)
469
+ const emailInput = page.getByLabel(/email address/i)
470
+ await expect(emailInput).toBeVisible()
471
+
472
+ // Name input should be focusable
473
+ await nameInput.focus()
474
+ await expect(nameInput).toBeFocused()
475
+
476
+ // Note: Email input may be disabled for OAuth-only users, so we just verify
477
+ // it has a proper label association (which getByLabel already confirms)
478
+ })
479
+
480
+ test('interactive elements are focusable', async ({ page }) => {
481
+ await page.goto('/integrations')
482
+
483
+ // Wait for the page heading to ensure content is loaded
484
+ const pageHeading = page.getByRole('heading', { level: 1, name: /integrations/i })
485
+ await expect(pageHeading).toBeVisible()
486
+
487
+ // Focus on the search input first to establish a starting point
488
+ const searchInput = page.getByPlaceholder(/search integrations/i)
489
+ await expect(searchInput).toBeVisible()
490
+ await searchInput.focus()
491
+ await expect(searchInput).toBeFocused()
492
+
493
+ // Press Tab to move to next interactive element
494
+ await page.keyboard.press('Tab')
495
+
496
+ // Verify something is focused after Tab
497
+ const focusedElement = page.locator(':focus')
498
+ await expect(focusedElement).toBeVisible()
499
+
500
+ // Continue tabbing to verify multiple interactive elements are focusable
501
+ await page.keyboard.press('Tab')
502
+ const secondFocusedElement = page.locator(':focus')
503
+ await expect(secondFocusedElement).toBeVisible()
504
+ })
505
+ })
506
+
507
+ test.describe('Live Regions and Announcements', () => {
508
+ test.beforeEach(async ({ page }) => {
509
+ const authenticated = await isAuthenticated(page)
510
+ test.skip(!authenticated, 'No authenticated session available')
511
+ })
512
+
513
+ test('toast notifications have role="status"', async ({ page }) => {
514
+ await page.goto('/settings')
515
+
516
+ // Trigger a toast by saving settings
517
+ const nameInput = page.getByLabel(/full name/i)
518
+ await nameInput.fill('Test User Updated')
519
+
520
+ const saveButton = page.getByRole('button', { name: /save changes/i })
521
+ await saveButton.click()
522
+
523
+ // Toast should appear with proper role
524
+ const toast = page.getByRole('status')
525
+ await expect(toast.first()).toBeVisible({ timeout: 5000 })
526
+ })
527
+
528
+ test('loading states are announced', async ({ page }) => {
529
+ await page.goto('/settings')
530
+
531
+ // Check that buttons have loading indicators when submitting
532
+ const saveButton = page.getByRole('button', { name: /save changes/i })
533
+
534
+ // Button should indicate loading state accessibly
535
+ // Either through aria-busy, aria-disabled, or visible spinner
536
+ await saveButton.click()
537
+
538
+ // The button should either be disabled or have aria-busy during loading
539
+ const isDisabled = await saveButton.isDisabled()
540
+ const ariaBusy = await saveButton.getAttribute('aria-busy')
541
+ const hasSpinner = await saveButton.locator('[class*="spinner"], [class*="animate-spin"]').count()
542
+
543
+ // At least one loading indicator should be present
544
+ expect(isDisabled || ariaBusy === 'true' || hasSpinner > 0).toBe(true)
545
+ })
546
+ })
547
+
548
+ test.describe('Touch Targets', () => {
549
+ test.beforeEach(async ({ page }) => {
550
+ const authenticated = await isAuthenticated(page)
551
+ test.skip(!authenticated, 'No authenticated session available')
552
+ })
553
+
554
+ test('buttons meet minimum touch target size (44x44)', async ({ page }) => {
555
+ await page.goto('/dashboard')
556
+
557
+ // Get all buttons
558
+ const buttons = page.getByRole('button')
559
+ const count = await buttons.count()
560
+
561
+ // Check a sample of buttons for adequate size
562
+ const samplesToCheck = Math.min(count, 10)
563
+ let smallButtonsFound = 0
564
+
565
+ for (let i = 0; i < samplesToCheck; i++) {
566
+ const button = buttons.nth(i)
567
+ if (await button.isVisible()) {
568
+ const box = await button.boundingBox()
569
+ if (box) {
570
+ // WCAG recommends 44x44 minimum touch target
571
+ // We'll log a warning if smaller, but not fail
572
+ if (box.width < 44 || box.height < 44) {
573
+ smallButtonsFound++
574
+ }
575
+ }
576
+ }
577
+ }
578
+
579
+ // Allow some small icon buttons, but most should be adequate size
580
+ expect(smallButtonsFound).toBeLessThan(samplesToCheck / 2)
581
+ })
582
+
583
+ test('links have adequate click area', async ({ page }) => {
584
+ await page.goto('/dashboard')
585
+
586
+ // Check navigation links
587
+ const navLinks = page.locator('nav').getByRole('link')
588
+ const count = await navLinks.count()
589
+
590
+ for (let i = 0; i < Math.min(count, 5); i++) {
591
+ const link = navLinks.nth(i)
592
+ if (await link.isVisible()) {
593
+ const box = await link.boundingBox()
594
+ if (box) {
595
+ // Links should have reasonable padding for touch
596
+ expect(box.height).toBeGreaterThanOrEqual(32)
597
+ }
598
+ }
599
+ }
600
+ })
601
+ })
602
+
603
+ test.describe('Image Accessibility', () => {
604
+ test.beforeEach(async ({ page }) => {
605
+ const authenticated = await isAuthenticated(page)
606
+ test.skip(!authenticated, 'No authenticated session available')
607
+ })
608
+
609
+ test('all images have alt attributes', async ({ page }) => {
610
+ await page.goto('/dashboard')
611
+
612
+ // Find all images
613
+ const images = page.locator('img')
614
+ const count = await images.count()
615
+
616
+ for (let i = 0; i < count; i++) {
617
+ const img = images.nth(i)
618
+ if (await img.isVisible()) {
619
+ // Image should have alt attribute (can be empty for decorative)
620
+ await expect(img).toHaveAttribute('alt')
621
+ }
622
+ }
623
+ })
624
+
625
+ test('SVG icons have accessible roles or are hidden', async ({ page }) => {
626
+ await page.goto('/dashboard')
627
+
628
+ // Find SVG elements
629
+ const svgs = page.locator('svg')
630
+ const count = await svgs.count()
631
+
632
+ for (let i = 0; i < Math.min(count, 10); i++) {
633
+ const svg = svgs.nth(i)
634
+ if (await svg.isVisible()) {
635
+ // SVG should either have role="img" with accessible name
636
+ // or be aria-hidden="true" if decorative
637
+ const role = await svg.getAttribute('role')
638
+ const ariaHidden = await svg.getAttribute('aria-hidden')
639
+ const ariaLabel = await svg.getAttribute('aria-label')
640
+ const title = await svg.locator('title').count()
641
+
642
+ // Either hidden or has accessible name
643
+ const isAccessible = ariaHidden === 'true' ||
644
+ role === 'img' ||
645
+ ariaLabel !== null ||
646
+ title > 0
647
+
648
+ // Most icons in buttons are decorative (text provides meaning)
649
+ // so aria-hidden is acceptable
650
+ expect(isAccessible || ariaHidden === null).toBe(true)
651
+ }
652
+ }
653
+ })
654
+ })
655
+
656
+ test.describe('Reduced Motion', () => {
657
+ test.beforeEach(async ({ page }) => {
658
+ const authenticated = await isAuthenticated(page)
659
+ test.skip(!authenticated, 'No authenticated session available')
660
+ })
661
+
662
+ test('app respects prefers-reduced-motion', async ({ page }) => {
663
+ // Emulate reduced motion preference
664
+ await page.emulateMedia({ reducedMotion: 'reduce' })
665
+ await page.goto('/dashboard')
666
+
667
+ // Check that the app loaded successfully with reduced motion
668
+ await expect(page.getByRole('heading', { name: /dashboard/i })).toBeVisible()
669
+
670
+ // Animations should be disabled or reduced
671
+ // Check sidebar collapse - should not have transition
672
+ const sidebar = page.locator('aside')
673
+ await expect(sidebar).toBeVisible()
674
+
675
+ // The app should be fully functional with reduced motion
676
+ const navLinks = page.locator('nav').getByRole('link')
677
+ await expect(navLinks.first()).toBeVisible()
678
+ })
679
+ })
680
+
681
+ test.describe('Color Independence', () => {
682
+ test.beforeEach(async ({ page }) => {
683
+ const authenticated = await isAuthenticated(page)
684
+ test.skip(!authenticated, 'No authenticated session available')
685
+ })
686
+
687
+ test('status indicators have text/icon alternatives to color', async ({ page }) => {
688
+ await page.goto('/integrations')
689
+
690
+ // Check connected status - should have "Connected" badge text, not just green color
691
+ const connectedBadge = page.locator('[class*="Badge"]', { hasText: /connected/i })
692
+ const count = await connectedBadge.count()
693
+
694
+ if (count > 0) {
695
+ await expect(connectedBadge.first()).toBeVisible()
696
+ // Badge has text, so color is not the only indicator
697
+ const text = await connectedBadge.first().textContent()
698
+ expect(text?.toLowerCase()).toContain('connected')
699
+ }
700
+ })
701
+
702
+ test('form validation errors have text descriptions', async ({ page }) => {
703
+ await page.goto('/team')
704
+
705
+ // Open invite dialog
706
+ await page.getByRole('button', { name: /invite member/i }).click()
707
+ const dialog = page.getByRole('dialog')
708
+ await expect(dialog).toBeVisible()
709
+
710
+ // Email input should have label for screen readers
711
+ const emailInput = page.getByLabel(/email address/i)
712
+ await expect(emailInput).toBeVisible()
713
+
714
+ // If we had invalid input, error should be in text, not just color
715
+ // For now, verify label exists
716
+ const label = page.locator('label', { hasText: /email/i })
717
+ await expect(label).toBeVisible()
718
+ })
719
+
720
+ test('role badges convey meaning without color', async ({ page }) => {
721
+ await page.goto('/team')
722
+
723
+ // Role badges should have text (admin, member, owner)
724
+ const badges = page.locator('[class*="Badge"]')
725
+ const count = await badges.count()
726
+
727
+ for (let i = 0; i < Math.min(count, 5); i++) {
728
+ const badge = badges.nth(i)
729
+ if (await badge.isVisible()) {
730
+ const text = await badge.textContent()
731
+ // Badge should have readable text content
732
+ expect(text?.trim().length).toBeGreaterThan(0)
733
+ }
734
+ }
735
+ })
736
+ })
737
+
738
+ test.describe('Focus Visibility', () => {
739
+ test.beforeEach(async ({ page }) => {
740
+ const authenticated = await isAuthenticated(page)
741
+ test.skip(!authenticated, 'No authenticated session available')
742
+ })
743
+
744
+ test('focused elements have visible focus indicator', async ({ page }) => {
745
+ await page.goto('/dashboard')
746
+
747
+ // Tab to first interactive element
748
+ await page.keyboard.press('Tab')
749
+ const focused = page.locator(':focus')
750
+
751
+ if (await focused.count() > 0) {
752
+ // Get computed style to check for focus ring
753
+ const styles = await focused.evaluate((el) => {
754
+ const computed = window.getComputedStyle(el)
755
+ return {
756
+ outline: computed.outline,
757
+ outlineWidth: computed.outlineWidth,
758
+ boxShadow: computed.boxShadow,
759
+ borderColor: computed.borderColor,
760
+ }
761
+ })
762
+
763
+ // Element should have some visible focus indicator
764
+ // (outline, box-shadow, or border change)
765
+ const hasVisibleFocus =
766
+ styles.outlineWidth !== '0px' ||
767
+ styles.boxShadow !== 'none' ||
768
+ styles.borderColor !== 'transparent'
769
+
770
+ expect(hasVisibleFocus).toBe(true)
771
+ }
772
+ })
773
+
774
+ test('buttons show focus state distinctly from hover', async ({ page }) => {
775
+ await page.goto('/settings')
776
+
777
+ const saveButton = page.getByRole('button', { name: /save changes/i })
778
+
779
+ // Get normal state
780
+ const normalStyles = await saveButton.evaluate((el) => {
781
+ const computed = window.getComputedStyle(el)
782
+ return {
783
+ outline: computed.outline,
784
+ boxShadow: computed.boxShadow,
785
+ }
786
+ })
787
+
788
+ // Focus the button
789
+ await saveButton.focus()
790
+
791
+ // Get focused state
792
+ const focusedStyles = await saveButton.evaluate((el) => {
793
+ const computed = window.getComputedStyle(el)
794
+ return {
795
+ outline: computed.outline,
796
+ boxShadow: computed.boxShadow,
797
+ }
798
+ })
799
+
800
+ // Focus state should be different from normal state
801
+ const hasDistinctFocus =
802
+ focusedStyles.outline !== normalStyles.outline ||
803
+ focusedStyles.boxShadow !== normalStyles.boxShadow
804
+
805
+ expect(hasDistinctFocus).toBe(true)
806
+ })
807
+ })
808
+
809
+ test.describe('Semantic HTML', () => {
810
+ test.beforeEach(async ({ page }) => {
811
+ const authenticated = await isAuthenticated(page)
812
+ test.skip(!authenticated, 'No authenticated session available')
813
+ })
814
+
815
+ test('page uses semantic HTML elements', async ({ page }) => {
816
+ await page.goto('/dashboard')
817
+
818
+ // Check for semantic structure
819
+ const main = page.locator('main')
820
+ await expect(main).toBeVisible()
821
+
822
+ const nav = page.locator('nav')
823
+ await expect(nav.first()).toBeVisible()
824
+
825
+ const aside = page.locator('aside')
826
+ await expect(aside).toBeVisible()
827
+
828
+ // Headings should be present
829
+ const h1 = page.getByRole('heading', { level: 1 })
830
+ await expect(h1).toBeVisible()
831
+ })
832
+
833
+ test('lists use proper list elements', async ({ page }) => {
834
+ await page.goto('/team')
835
+
836
+ // Team members should be in a structured format
837
+ // Even if not using ul/li, should have role="list" or similar structure
838
+ const memberList = page.locator('[class*="divide-y"]')
839
+ await expect(memberList.first()).toBeVisible()
840
+
841
+ // Should contain multiple items
842
+ const items = memberList.first().locator('> div')
843
+ const count = await items.count()
844
+ expect(count).toBeGreaterThanOrEqual(1)
845
+ })
846
+
847
+ test('buttons are not links and links are not buttons', async ({ page }) => {
848
+ await page.goto('/dashboard')
849
+
850
+ // Buttons should be <button> elements
851
+ const buttons = page.getByRole('button')
852
+ const buttonCount = await buttons.count()
853
+
854
+ for (let i = 0; i < Math.min(buttonCount, 5); i++) {
855
+ const button = buttons.nth(i)
856
+ if (await button.isVisible()) {
857
+ const tagName = await button.evaluate((el) => el.tagName.toLowerCase())
858
+ // Should be button or have role="button"
859
+ const role = await button.getAttribute('role')
860
+ expect(tagName === 'button' || role === 'button').toBe(true)
861
+ }
862
+ }
863
+
864
+ // Links should be <a> elements
865
+ const links = page.getByRole('link')
866
+ const linkCount = await links.count()
867
+
868
+ for (let i = 0; i < Math.min(linkCount, 5); i++) {
869
+ const link = links.nth(i)
870
+ if (await link.isVisible()) {
871
+ const tagName = await link.evaluate((el) => el.tagName.toLowerCase())
872
+ expect(tagName).toBe('a')
873
+
874
+ // Links should have href
875
+ const href = await link.getAttribute('href')
876
+ expect(href).toBeTruthy()
877
+ }
878
+ }
879
+ })
880
+ })
881
+
882
+ test.describe('Text Accessibility', () => {
883
+ test.beforeEach(async ({ page }) => {
884
+ const authenticated = await isAuthenticated(page)
885
+ test.skip(!authenticated, 'No authenticated session available')
886
+ })
887
+
888
+ test('text can be resized up to 200% without loss', async ({ page }) => {
889
+ await page.goto('/dashboard')
890
+
891
+ // Zoom to 200%
892
+ await page.evaluate(() => {
893
+ document.body.style.zoom = '2'
894
+ })
895
+
896
+ // Content should still be visible and usable
897
+ await expect(page.getByRole('heading', { name: /dashboard/i })).toBeVisible()
898
+
899
+ // Navigation should still work
900
+ const navLinks = page.locator('nav').getByRole('link')
901
+ await expect(navLinks.first()).toBeVisible()
902
+ })
903
+
904
+ test('line height and spacing support readability', async ({ page }) => {
905
+ await page.goto('/dashboard')
906
+
907
+ // Check paragraph text has adequate line height
908
+ const paragraphs = page.locator('p')
909
+ const count = await paragraphs.count()
910
+
911
+ if (count > 0) {
912
+ const styles = await paragraphs.first().evaluate((el) => {
913
+ const computed = window.getComputedStyle(el)
914
+ const fontSize = parseFloat(computed.fontSize)
915
+ const lineHeight = parseFloat(computed.lineHeight)
916
+ return { fontSize, lineHeight, ratio: lineHeight / fontSize }
917
+ })
918
+
919
+ // Line height should be at least 1.5 times font size for readability
920
+ // (WCAG AAA recommends 1.5, AA allows 1.2)
921
+ expect(styles.ratio).toBeGreaterThanOrEqual(1.2)
922
+ }
923
+ })
924
+ })
925
+ })