@nextsparkjs/theme-default 0.1.0-beta.2 → 0.1.0-beta.21

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 (222) hide show
  1. package/package.json +8 -4
  2. package/templates/(public)/page.tsx +1 -1
  3. package/tests/cypress/e2e/_devtools/access.bdd.md +262 -0
  4. package/tests/cypress/e2e/_devtools/access.cy.ts +171 -0
  5. package/tests/cypress/e2e/_devtools/navigation.bdd.md +261 -0
  6. package/tests/cypress/e2e/_devtools/navigation.cy.ts +157 -0
  7. package/tests/cypress/e2e/_devtools/pages.bdd.md +303 -0
  8. package/tests/cypress/e2e/_devtools/pages.cy.ts +184 -0
  9. package/tests/cypress/e2e/_docs/README.md +215 -0
  10. package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin-teams.narration.json +155 -0
  11. package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin.cy.ts +390 -0
  12. package/tests/cypress/e2e/_docs/tutorials/teams-system.doc.cy.ts +349 -0
  13. package/tests/cypress/e2e/_docs/tutorials/teams-system.narration.json +165 -0
  14. package/tests/cypress/e2e/_selectors/auth.cy.ts +306 -0
  15. package/tests/cypress/e2e/_selectors/billing.cy.ts +89 -0
  16. package/tests/cypress/e2e/_selectors/dashboard-mobile.cy.ts +113 -0
  17. package/tests/cypress/e2e/_selectors/dashboard-navigation.cy.ts +89 -0
  18. package/tests/cypress/e2e/_selectors/dashboard-sidebar.cy.ts +60 -0
  19. package/tests/cypress/e2e/_selectors/dashboard-topnav.cy.ts +146 -0
  20. package/tests/cypress/e2e/_selectors/devtools.cy.ts +210 -0
  21. package/tests/cypress/e2e/_selectors/global-search.cy.ts +88 -0
  22. package/tests/cypress/e2e/_selectors/pages-editor.cy.ts +179 -0
  23. package/tests/cypress/e2e/_selectors/posts-editor.cy.ts +282 -0
  24. package/tests/cypress/e2e/_selectors/public.cy.ts +112 -0
  25. package/tests/cypress/e2e/_selectors/settings-api-keys.cy.ts +228 -0
  26. package/tests/cypress/e2e/_selectors/settings-billing.cy.ts +105 -0
  27. package/tests/cypress/e2e/_selectors/settings-layout.cy.ts +119 -0
  28. package/tests/cypress/e2e/_selectors/settings-password.cy.ts +71 -0
  29. package/tests/cypress/e2e/_selectors/settings-profile.cy.ts +82 -0
  30. package/tests/cypress/e2e/_selectors/settings-teams.cy.ts +68 -0
  31. package/tests/cypress/e2e/_selectors/superadmin.cy.ts +185 -0
  32. package/tests/cypress/e2e/_selectors/tasks.cy.ts +242 -0
  33. package/tests/cypress/e2e/_selectors/taxonomies.cy.ts +126 -0
  34. package/tests/cypress/e2e/_selectors/teams.cy.ts +142 -0
  35. package/tests/cypress/e2e/_superadmin/all-teams.bdd.md +261 -0
  36. package/tests/cypress/e2e/_superadmin/all-teams.cy.ts +177 -0
  37. package/tests/cypress/e2e/_superadmin/all-users.bdd.md +406 -0
  38. package/tests/cypress/e2e/_superadmin/all-users.cy.ts +294 -0
  39. package/tests/cypress/e2e/_superadmin/dashboard.bdd.md +235 -0
  40. package/tests/cypress/e2e/_superadmin/dashboard.cy.ts +149 -0
  41. package/tests/cypress/e2e/_superadmin/subscriptions-overview.bdd.md +290 -0
  42. package/tests/cypress/e2e/_superadmin/subscriptions-overview.cy.ts +194 -0
  43. package/tests/cypress/e2e/ai/ai-usage.cy.ts +209 -0
  44. package/tests/cypress/e2e/ai/chat-api.cy.ts +107 -0
  45. package/tests/cypress/e2e/ai/guardrails.cy.ts +332 -0
  46. package/tests/cypress/e2e/api/billing/BillingAPIController.js +319 -0
  47. package/tests/cypress/e2e/api/billing/check-action.cy.ts +326 -0
  48. package/tests/cypress/e2e/api/billing/checkout.cy.ts +358 -0
  49. package/tests/cypress/e2e/api/billing/lifecycle.cy.ts +423 -0
  50. package/tests/cypress/e2e/api/billing/plans/README.md +345 -0
  51. package/tests/cypress/e2e/api/billing/plans/business.cy.ts +412 -0
  52. package/tests/cypress/e2e/api/billing/plans/downgrade.cy.ts +510 -0
  53. package/tests/cypress/e2e/api/billing/plans/fixtures/billing-plans.json +163 -0
  54. package/tests/cypress/e2e/api/billing/plans/free.cy.ts +500 -0
  55. package/tests/cypress/e2e/api/billing/plans/pro.cy.ts +497 -0
  56. package/tests/cypress/e2e/api/billing/plans/starter.cy.ts +342 -0
  57. package/tests/cypress/e2e/api/billing/portal.cy.ts +313 -0
  58. package/tests/cypress/e2e/api/devtools/registries.bdd.md +300 -0
  59. package/tests/cypress/e2e/api/devtools/registries.cy.ts +368 -0
  60. package/tests/cypress/e2e/api/entities/blocks-scope.cy.ts +396 -0
  61. package/tests/cypress/e2e/api/entities/customers-crud.cy.ts +648 -0
  62. package/tests/cypress/e2e/api/entities/customers-metas.cy.ts +839 -0
  63. package/tests/cypress/e2e/api/entities/pages-crud.cy.ts +425 -0
  64. package/tests/cypress/e2e/api/entities/pages-status.cy.ts +335 -0
  65. package/tests/cypress/e2e/api/entities/post-categories-crud.cy.ts +610 -0
  66. package/tests/cypress/e2e/api/entities/posts-crud.cy.ts +709 -0
  67. package/tests/cypress/e2e/api/entities/posts-status.cy.ts +396 -0
  68. package/tests/cypress/e2e/api/entities/tasks-crud.cy.ts +602 -0
  69. package/tests/cypress/e2e/api/entities/tasks-metas.cy.ts +878 -0
  70. package/tests/cypress/e2e/api/entities/users-crud.cy.ts +469 -0
  71. package/tests/cypress/e2e/api/entities/users-metas.cy.ts +913 -0
  72. package/tests/cypress/e2e/api/entities/users-security.cy.ts +375 -0
  73. package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.bdd.md +375 -0
  74. package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.cy.ts +346 -0
  75. package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.bdd.md +451 -0
  76. package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.cy.ts +447 -0
  77. package/tests/cypress/e2e/api/scheduled-actions/scheduling.bdd.md +649 -0
  78. package/tests/cypress/e2e/api/scheduled-actions/scheduling.cy.ts +333 -0
  79. package/tests/cypress/e2e/api/settings/api-keys.crud.cy.ts +923 -0
  80. package/tests/cypress/e2e/uat/auth/app-roles/developer-login.bdd.md +231 -0
  81. package/tests/cypress/e2e/uat/auth/app-roles/developer-login.cy.ts +144 -0
  82. package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.bdd.md +118 -0
  83. package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.cy.ts +84 -0
  84. package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.bdd.md +288 -0
  85. package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.cy.ts +188 -0
  86. package/tests/cypress/e2e/uat/auth/login-logout.bdd.md +160 -0
  87. package/tests/cypress/e2e/uat/auth/login-logout.cy.ts +116 -0
  88. package/tests/cypress/e2e/uat/auth/password-reset.bdd.md +289 -0
  89. package/tests/cypress/e2e/uat/auth/password-reset.cy.ts +200 -0
  90. package/tests/cypress/e2e/uat/auth/team-roles/admin-login.bdd.md +225 -0
  91. package/tests/cypress/e2e/uat/auth/team-roles/admin-login.cy.ts +148 -0
  92. package/tests/cypress/e2e/uat/auth/team-roles/member-login.bdd.md +251 -0
  93. package/tests/cypress/e2e/uat/auth/team-roles/member-login.cy.ts +163 -0
  94. package/tests/cypress/e2e/uat/auth/team-roles/owner-login.bdd.md +231 -0
  95. package/tests/cypress/e2e/uat/auth/team-roles/owner-login.cy.ts +141 -0
  96. package/tests/cypress/e2e/uat/billing/extended.bdd.md +273 -0
  97. package/tests/cypress/e2e/uat/billing/extended.cy.ts +209 -0
  98. package/tests/cypress/e2e/uat/billing/feature-gates.bdd.md +407 -0
  99. package/tests/cypress/e2e/uat/billing/feature-gates.cy.ts +307 -0
  100. package/tests/cypress/e2e/uat/billing/page.bdd.md +329 -0
  101. package/tests/cypress/e2e/uat/billing/page.cy.ts +250 -0
  102. package/tests/cypress/e2e/uat/billing/status.bdd.md +190 -0
  103. package/tests/cypress/e2e/uat/billing/status.cy.ts +145 -0
  104. package/tests/cypress/e2e/uat/billing/team-switch.bdd.md +156 -0
  105. package/tests/cypress/e2e/uat/billing/team-switch.cy.ts +122 -0
  106. package/tests/cypress/e2e/uat/billing/usage.bdd.md +218 -0
  107. package/tests/cypress/e2e/uat/billing/usage.cy.ts +176 -0
  108. package/tests/cypress/e2e/uat/blocks/hero.bdd.md +124 -0
  109. package/tests/cypress/e2e/uat/blocks/hero.cy.ts +56 -0
  110. package/tests/cypress/e2e/uat/devtools/api-tester.cy.ts +390 -0
  111. package/tests/cypress/e2e/uat/entities/customers/member.bdd.md +275 -0
  112. package/tests/cypress/e2e/uat/entities/customers/member.cy.ts +122 -0
  113. package/tests/cypress/e2e/uat/entities/customers/owner.bdd.md +243 -0
  114. package/tests/cypress/e2e/uat/entities/customers/owner.cy.ts +165 -0
  115. package/tests/cypress/e2e/uat/entities/pages/block-crud.bdd.md +476 -0
  116. package/tests/cypress/e2e/uat/entities/pages/block-crud.cy.ts +486 -0
  117. package/tests/cypress/e2e/uat/entities/pages/block-editor.bdd.md +460 -0
  118. package/tests/cypress/e2e/uat/entities/pages/block-editor.cy.ts +301 -0
  119. package/tests/cypress/e2e/uat/entities/pages/list.bdd.md +432 -0
  120. package/tests/cypress/e2e/uat/entities/pages/list.cy.ts +273 -0
  121. package/tests/cypress/e2e/uat/entities/pages/public-rendering.bdd.md +696 -0
  122. package/tests/cypress/e2e/uat/entities/pages/public-rendering.cy.ts +340 -0
  123. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.bdd.md +161 -0
  124. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.cy.ts +104 -0
  125. package/tests/cypress/e2e/uat/entities/posts/categories.bdd.md +375 -0
  126. package/tests/cypress/e2e/uat/entities/posts/categories.cy.ts +241 -0
  127. package/tests/cypress/e2e/uat/entities/posts/editor.bdd.md +429 -0
  128. package/tests/cypress/e2e/uat/entities/posts/editor.cy.ts +257 -0
  129. package/tests/cypress/e2e/uat/entities/posts/list.bdd.md +340 -0
  130. package/tests/cypress/e2e/uat/entities/posts/list.cy.ts +177 -0
  131. package/tests/cypress/e2e/uat/entities/posts/public.bdd.md +614 -0
  132. package/tests/cypress/e2e/uat/entities/posts/public.cy.ts +249 -0
  133. package/tests/cypress/e2e/uat/entities/tasks/member.bdd.md +222 -0
  134. package/tests/cypress/e2e/uat/entities/tasks/member.cy.ts +165 -0
  135. package/tests/cypress/e2e/uat/entities/tasks/owner.bdd.md +419 -0
  136. package/tests/cypress/e2e/uat/entities/tasks/owner.cy.ts +191 -0
  137. package/tests/cypress/e2e/uat/roles/editor-role.bdd.md +552 -0
  138. package/tests/cypress/e2e/uat/roles/editor-role.cy.ts +210 -0
  139. package/tests/cypress/e2e/uat/roles/member-restrictions.bdd.md +450 -0
  140. package/tests/cypress/e2e/uat/roles/member-restrictions.cy.ts +189 -0
  141. package/tests/cypress/e2e/uat/roles/owner-full-crud.bdd.md +530 -0
  142. package/tests/cypress/e2e/uat/roles/owner-full-crud.cy.ts +247 -0
  143. package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.bdd.md +736 -0
  144. package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.cy.ts +740 -0
  145. package/tests/cypress/e2e/uat/teams/roles-matrix.bdd.md +553 -0
  146. package/tests/cypress/e2e/uat/teams/roles-matrix.cy.ts +185 -0
  147. package/tests/cypress/e2e/uat/teams/switcher.bdd.md +1151 -0
  148. package/tests/cypress/e2e/uat/teams/switcher.cy.ts +497 -0
  149. package/tests/cypress/e2e/uat/teams/team-switcher.md +198 -0
  150. package/tests/cypress/fixtures/blocks.json +218 -0
  151. package/tests/cypress/fixtures/entities.json +78 -0
  152. package/tests/cypress/fixtures/page-builder.json +21 -0
  153. package/tests/cypress/src/components/CategoriesPOM.ts +382 -0
  154. package/tests/cypress/src/components/CustomersPOM.ts +439 -0
  155. package/tests/cypress/src/components/DevKeyringPOM.ts +160 -0
  156. package/tests/cypress/src/components/EntityForm.ts +375 -0
  157. package/tests/cypress/src/components/EntityList.ts +389 -0
  158. package/tests/cypress/src/components/PageBuilderPOM.ts +710 -0
  159. package/tests/cypress/src/components/PostEditorPOM.ts +370 -0
  160. package/tests/cypress/src/components/PostsListPOM.ts +223 -0
  161. package/tests/cypress/src/components/PublicPagePOM.ts +447 -0
  162. package/tests/cypress/src/components/PublicPostPOM.ts +146 -0
  163. package/tests/cypress/src/components/TasksPOM.ts +272 -0
  164. package/tests/cypress/src/components/TeamSwitcherPOM.ts +450 -0
  165. package/tests/cypress/src/components/index.ts +21 -0
  166. package/tests/cypress/src/controllers/ApiKeysAPIController.js +178 -0
  167. package/tests/cypress/src/controllers/BaseAPIController.js +317 -0
  168. package/tests/cypress/src/controllers/CustomerAPIController.js +251 -0
  169. package/tests/cypress/src/controllers/PagesAPIController.js +226 -0
  170. package/tests/cypress/src/controllers/PostsAPIController.js +250 -0
  171. package/tests/cypress/src/controllers/TaskAPIController.js +240 -0
  172. package/tests/cypress/src/controllers/UsersAPIController.js +242 -0
  173. package/tests/cypress/src/controllers/index.js +25 -0
  174. package/tests/cypress/src/core/AuthPOM.ts +450 -0
  175. package/tests/cypress/src/core/BasePOM.ts +86 -0
  176. package/tests/cypress/src/core/BlockEditorBasePOM.ts +576 -0
  177. package/tests/cypress/src/core/DashboardEntityPOM.ts +692 -0
  178. package/tests/cypress/src/core/index.ts +14 -0
  179. package/tests/cypress/src/entities/CustomersPOM.ts +172 -0
  180. package/tests/cypress/src/entities/PagesPOM.ts +137 -0
  181. package/tests/cypress/src/entities/PostsPOM.ts +137 -0
  182. package/tests/cypress/src/entities/TasksPOM.ts +176 -0
  183. package/tests/cypress/src/entities/index.ts +14 -0
  184. package/tests/cypress/src/features/BillingPOM.ts +385 -0
  185. package/tests/cypress/src/features/DashboardPOM.ts +245 -0
  186. package/tests/cypress/src/features/DevtoolsPOM.ts +739 -0
  187. package/tests/cypress/src/features/PageBuilderPOM.ts +263 -0
  188. package/tests/cypress/src/features/PostEditorPOM.ts +313 -0
  189. package/tests/cypress/src/features/ScheduledActionsPOM.ts +463 -0
  190. package/tests/cypress/src/features/SettingsPOM.ts +362 -0
  191. package/tests/cypress/src/features/SuperadminPOM.ts +331 -0
  192. package/tests/cypress/src/features/SuperadminTeamRolesPOM.ts +285 -0
  193. package/tests/cypress/src/features/index.ts +28 -0
  194. package/tests/cypress/src/helpers/ApiInterceptor.ts +177 -0
  195. package/tests/cypress/src/index.ts +101 -0
  196. package/tests/cypress/src/pages/dashboard/Dashboard.js +677 -0
  197. package/tests/cypress/src/pages/dashboard/DashboardPage.js +43 -0
  198. package/tests/cypress/src/pages/dashboard/DashboardStats.js +546 -0
  199. package/tests/cypress/src/pages/dashboard/index.js +6 -0
  200. package/tests/cypress/src/pages/index.js +5 -0
  201. package/tests/cypress/src/pages/public/FeaturesPage.js +28 -0
  202. package/tests/cypress/src/pages/public/LandingPage.js +69 -0
  203. package/tests/cypress/src/pages/public/PricingPage.js +33 -0
  204. package/tests/cypress/src/pages/public/index.js +6 -0
  205. package/tests/cypress/src/selectors.ts +46 -0
  206. package/tests/cypress/src/session-helpers.ts +500 -0
  207. package/tests/cypress/support/doc-commands.ts +260 -0
  208. package/tests/cypress.config.ts +150 -0
  209. package/tests/jest/components/post-header.test.tsx +377 -0
  210. package/tests/jest/config/role-config.test.ts +529 -0
  211. package/tests/jest/jest.config.ts +81 -0
  212. package/tests/jest/langchain/COVERAGE.md +372 -0
  213. package/tests/jest/langchain/guardrails.test.ts +465 -0
  214. package/tests/jest/langchain/streaming.test.ts +367 -0
  215. package/tests/jest/langchain/token-tracker.test.ts +455 -0
  216. package/tests/jest/langchain/tracer-callbacks.test.ts +881 -0
  217. package/tests/jest/langchain/tracer.test.ts +823 -0
  218. package/tests/jest/user-roles/role-helpers.test.ts +432 -0
  219. package/tests/jest/validation/categories.test.ts +429 -0
  220. package/tests/jest/validation/posts.test.ts +546 -0
  221. package/tests/tsconfig.json +15 -0
  222. package/LICENSE +0 -21
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Theme Selectors for Cypress Tests
3
+ *
4
+ * This file re-exports from the main selectors in lib/.
5
+ * The lib/selectors.ts is the source of truth, placed there so
6
+ * block components can import it (tests/ is excluded from TypeScript).
7
+ *
8
+ * Architecture:
9
+ * - Core selectors: `core/lib/test/core-selectors.ts`
10
+ * - Theme selectors (source): `lib/selectors.ts`
11
+ * - Theme selectors (tests): This file (re-exports)
12
+ *
13
+ * @example POM usage:
14
+ * ```typescript
15
+ * import { cySelector, sel, SELECTORS } from '../selectors'
16
+ *
17
+ * class MyPOM extends BasePOM {
18
+ * get elements() {
19
+ * return {
20
+ * loginForm: cySelector('auth.login.form'),
21
+ * submitButton: cySelector('auth.login.submit'),
22
+ * heroBlock: cySelector('blocks.hero.container'),
23
+ * }
24
+ * }
25
+ * }
26
+ * ```
27
+ */
28
+
29
+ // Re-export everything from the lib selectors
30
+ export {
31
+ BLOCK_SELECTORS,
32
+ THEME_SELECTORS,
33
+ SELECTORS,
34
+ sel,
35
+ s,
36
+ selDev,
37
+ cySelector,
38
+ entitySelectors,
39
+ CORE_SELECTORS,
40
+ } from '../../../lib/selectors'
41
+
42
+ export type {
43
+ ThemeSelectorsType,
44
+ BlockSelectorsType,
45
+ Replacements,
46
+ } from '../../../lib/selectors'
@@ -0,0 +1,500 @@
1
+ /**
2
+ * Default Theme Session Helpers
3
+ *
4
+ * Direct login functions for Default theme tests using theme-specific users.
5
+ * These helpers don't depend on ACTIVE_THEME environment variable.
6
+ *
7
+ * EXPERIMENTAL: Uses API-based login instead of UI login.
8
+ * Hypothesis: API login is faster and more stable than DevKeyring UI login,
9
+ * especially with slow dev servers (Turbopack on-demand compilation).
10
+ *
11
+ * Test file: cypress/e2e/_experimental/api-login-test.cy.ts
12
+ * Fallback: If API fails, falls back to UI login with DevKeyring.
13
+ *
14
+ * IMPORTANT: After login, this helper sets activeTeamId in localStorage.
15
+ * This is required because all entity API calls include x-team-id header
16
+ * which is read from localStorage.activeTeamId (see core/lib/api/entities.ts).
17
+ * Without this, API calls return 400 "Team context required".
18
+ */
19
+
20
+ import { DevKeyringPOM as DevKeyring } from './components/DevKeyringPOM'
21
+
22
+ /**
23
+ * Default Theme Test Users
24
+ * Teams: Everpoint Labs, Ironvale Global, Riverstone Ventures
25
+ */
26
+ export const DEFAULT_THEME_USERS = {
27
+ OWNER: 'carlos.mendoza@nextspark.dev', // Everpoint Labs (owner), Riverstone (member)
28
+ ADMIN: 'james.wilson@nextspark.dev', // Everpoint Labs (admin)
29
+ MEMBER: 'emily.johnson@nextspark.dev', // Everpoint (member), Riverstone (admin)
30
+ EDITOR: 'diego.ramirez@nextspark.dev', // Everpoint Labs (editor) - custom role
31
+ VIEWER: 'sarah.davis@nextspark.dev', // Ironvale Global (viewer)
32
+ } as const
33
+
34
+ /**
35
+ * Core System Users (from core/migrations/090_sample_data.sql)
36
+ * These users have special global roles, not team-based roles
37
+ */
38
+ export const CORE_USERS = {
39
+ SUPERADMIN: 'superadmin@nextspark.dev', // Global superadmin role - Admin access
40
+ DEVELOPER: 'developer@nextspark.dev', // Global developer role - Dev Zone access
41
+ } as const
42
+
43
+ // Common password for demo users (team-based)
44
+ const TEST_PASSWORD = 'Test1234'
45
+
46
+ // Password for core system users (superadmin/developer)
47
+ const CORE_PASSWORD = 'Pandora1234'
48
+
49
+ // Extended timeout for dev server compilation
50
+ const API_TIMEOUT = 30000
51
+
52
+ /**
53
+ * Sets up team context after login (requires page to be loaded for localStorage access)
54
+ *
55
+ * This is CRITICAL for entity API calls to work:
56
+ * 1. Fetches user's teams from /api/v1/teams
57
+ * 2. Sets activeTeamId in localStorage (used by frontend to add x-team-id header)
58
+ * 3. Calls /api/v1/teams/switch to set server-side team cookie
59
+ *
60
+ * Without this, all entity API calls return 400 "Team context required"
61
+ *
62
+ * @param preferredRole - Optional role to filter teams by (e.g., 'member' to select team where user is member)
63
+ */
64
+ function setupTeamContext(preferredRole?: string) {
65
+ cy.request({
66
+ method: 'GET',
67
+ url: '/api/v1/teams',
68
+ timeout: API_TIMEOUT,
69
+ failOnStatusCode: false
70
+ }).then((teamsResponse) => {
71
+ if (teamsResponse.status === 200 && teamsResponse.body?.data?.length > 0) {
72
+ const teams = teamsResponse.body.data
73
+
74
+ // If preferredRole specified, find team where user has that role
75
+ let selectedTeam = teams[0]
76
+ if (preferredRole) {
77
+ const teamWithRole = teams.find((t: { role: string }) => t.role === preferredRole)
78
+ if (teamWithRole) {
79
+ selectedTeam = teamWithRole
80
+ cy.log(`✅ Found team with role "${preferredRole}": ${selectedTeam.name}`)
81
+ } else {
82
+ cy.log(`⚠️ No team found with role "${preferredRole}", using first team`)
83
+ }
84
+ }
85
+
86
+ const teamId = selectedTeam.id
87
+
88
+ cy.log(`✅ Setting active team: ${selectedTeam.name} (${teamId}) - role: ${selectedTeam.role}`)
89
+
90
+ // Set in localStorage (used by frontend buildHeaders() to add x-team-id)
91
+ cy.window().then((win) => {
92
+ win.localStorage.setItem('activeTeamId', teamId)
93
+ })
94
+
95
+ // Call teams/switch to set server-side cookie (for SSR/layouts)
96
+ cy.request({
97
+ method: 'POST',
98
+ url: '/api/v1/teams/switch',
99
+ body: { teamId },
100
+ timeout: API_TIMEOUT,
101
+ failOnStatusCode: false
102
+ })
103
+ } else {
104
+ cy.log(`⚠️ No teams found for user, API calls requiring team context will fail`)
105
+ }
106
+ })
107
+ }
108
+
109
+ /**
110
+ * Login via API (faster and more stable than UI)
111
+ * Returns true if API login succeeded, false if fallback to UI was needed.
112
+ *
113
+ * @param email - User email to login
114
+ * @param password - Optional password (defaults to TEST_PASSWORD for demo users)
115
+ */
116
+ function apiLogin(email: string, password: string = TEST_PASSWORD): Cypress.Chainable<boolean> {
117
+ return cy.request({
118
+ method: 'POST',
119
+ url: '/api/auth/sign-in/email',
120
+ body: { email, password },
121
+ timeout: API_TIMEOUT,
122
+ failOnStatusCode: false
123
+ }).then((response) => {
124
+ if (response.status === 200) {
125
+ return true
126
+ } else {
127
+ cy.log(`⚠️ API login failed with status ${response.status}, falling back to UI login`)
128
+ // Fallback to UI login if API fails
129
+ cy.visit('/login', { timeout: 60000 })
130
+ const devKeyring = new DevKeyring()
131
+ devKeyring.validateVisible()
132
+ devKeyring.quickLoginByEmail(email)
133
+ return false
134
+ }
135
+ })
136
+ }
137
+
138
+ /**
139
+ * Login as Default Theme Owner
140
+ * Session is cached and reused across tests
141
+ *
142
+ * Flow:
143
+ * 1. API login (or UI fallback)
144
+ * 2. Visit dashboard to load page context
145
+ * 3. Setup team context (sets localStorage.activeTeamId)
146
+ */
147
+ export function loginAsDefaultOwner() {
148
+ cy.session('default-owner-session', () => {
149
+ apiLogin(DEFAULT_THEME_USERS.OWNER).then((apiLoginSucceeded) => {
150
+ // If API login succeeded, we need to visit a page before setting localStorage
151
+ if (apiLoginSucceeded) {
152
+ cy.visit('/dashboard', { timeout: 60000 })
153
+ }
154
+ // URL assertion to ensure page loaded
155
+ cy.url().should('include', '/dashboard')
156
+ // Setup team context (requires page to be loaded for localStorage)
157
+ setupTeamContext()
158
+ })
159
+ }, {
160
+ validate: () => {
161
+ // Validate auth session exists
162
+ cy.request({
163
+ url: '/api/auth/get-session',
164
+ timeout: API_TIMEOUT,
165
+ failOnStatusCode: false
166
+ }).its('status').should('eq', 200)
167
+
168
+ // Validate team context exists in localStorage
169
+ // This ensures API calls will have x-team-id header
170
+ cy.window().then((win) => {
171
+ const teamId = win.localStorage.getItem('activeTeamId')
172
+ expect(teamId, 'activeTeamId should exist in localStorage').to.not.be.null
173
+ expect(teamId, 'activeTeamId should not be empty').to.not.be.empty
174
+ })
175
+ }
176
+ })
177
+ }
178
+
179
+ /**
180
+ * Login as Default Theme Admin
181
+ * Session is cached and reused across tests
182
+ */
183
+ export function loginAsDefaultAdmin() {
184
+ cy.session('default-admin-session', () => {
185
+ apiLogin(DEFAULT_THEME_USERS.ADMIN).then((apiLoginSucceeded) => {
186
+ if (apiLoginSucceeded) {
187
+ cy.visit('/dashboard', { timeout: 60000 })
188
+ }
189
+ cy.url().should('include', '/dashboard')
190
+ setupTeamContext()
191
+ })
192
+ }, {
193
+ validate: () => {
194
+ cy.request({
195
+ url: '/api/auth/get-session',
196
+ timeout: API_TIMEOUT,
197
+ failOnStatusCode: false
198
+ }).its('status').should('eq', 200)
199
+ }
200
+ })
201
+ }
202
+
203
+ /**
204
+ * Login as Default Theme Member
205
+ * Session is cached and reused across tests
206
+ *
207
+ * Note: Emily Johnson is member of Everpoint but admin of Riverstone.
208
+ * We explicitly select the team where she has 'member' role.
209
+ */
210
+ export function loginAsDefaultMember() {
211
+ cy.session('default-member-session', () => {
212
+ apiLogin(DEFAULT_THEME_USERS.MEMBER).then((apiLoginSucceeded) => {
213
+ if (apiLoginSucceeded) {
214
+ cy.visit('/dashboard', { timeout: 60000 })
215
+ }
216
+ cy.url().should('include', '/dashboard')
217
+ // Explicitly select team where user is member (not admin)
218
+ setupTeamContext('member')
219
+ })
220
+ }, {
221
+ validate: () => {
222
+ cy.request({
223
+ url: '/api/auth/get-session',
224
+ timeout: API_TIMEOUT,
225
+ failOnStatusCode: false
226
+ }).its('status').should('eq', 200)
227
+ }
228
+ })
229
+ }
230
+
231
+ /**
232
+ * Login as Default Theme Viewer
233
+ * Session is cached and reused across tests
234
+ */
235
+ export function loginAsDefaultViewer() {
236
+ cy.session('default-viewer-session', () => {
237
+ apiLogin(DEFAULT_THEME_USERS.VIEWER).then((apiLoginSucceeded) => {
238
+ if (apiLoginSucceeded) {
239
+ cy.visit('/dashboard', { timeout: 60000 })
240
+ }
241
+ cy.url().should('include', '/dashboard')
242
+ setupTeamContext()
243
+ })
244
+ }, {
245
+ validate: () => {
246
+ cy.request({
247
+ url: '/api/auth/get-session',
248
+ timeout: API_TIMEOUT,
249
+ failOnStatusCode: false
250
+ }).its('status').should('eq', 200)
251
+ }
252
+ })
253
+ }
254
+
255
+ /**
256
+ * Login as Default Theme Editor
257
+ * Session is cached and reused across tests
258
+ *
259
+ * Editor is a custom role with limited permissions:
260
+ * - Can view/list customers but cannot create/update/delete
261
+ * - Cannot access Admin or Dev Zone
262
+ */
263
+ export function loginAsDefaultEditor() {
264
+ cy.session('default-editor-session', () => {
265
+ apiLogin(DEFAULT_THEME_USERS.EDITOR).then((apiLoginSucceeded) => {
266
+ if (apiLoginSucceeded) {
267
+ cy.visit('/dashboard', { timeout: 60000 })
268
+ }
269
+ cy.url().should('include', '/dashboard')
270
+ setupTeamContext()
271
+ })
272
+ }, {
273
+ validate: () => {
274
+ cy.request({
275
+ url: '/api/auth/get-session',
276
+ timeout: API_TIMEOUT,
277
+ failOnStatusCode: false
278
+ }).its('status').should('eq', 200)
279
+ }
280
+ })
281
+ }
282
+
283
+ /**
284
+ * Login as Superadmin (core system user)
285
+ * Session is cached and reused across tests
286
+ *
287
+ * Superadmin has global access:
288
+ * - Full Admin access
289
+ * - Not team-based (no setupTeamContext needed)
290
+ */
291
+ export function loginAsDefaultSuperadmin() {
292
+ cy.session('default-superadmin-session', () => {
293
+ apiLogin(CORE_USERS.SUPERADMIN, CORE_PASSWORD).then((apiLoginSucceeded) => {
294
+ if (apiLoginSucceeded) {
295
+ cy.visit('/superadmin', { timeout: 60000 })
296
+ }
297
+ // Superadmin should land on superadmin panel or dashboard
298
+ cy.url().should('satisfy', (url: string) => {
299
+ return url.includes('/superadmin') || url.includes('/dashboard')
300
+ })
301
+ })
302
+ }, {
303
+ validate: () => {
304
+ cy.request({
305
+ url: '/api/auth/get-session',
306
+ timeout: API_TIMEOUT,
307
+ failOnStatusCode: false
308
+ }).its('status').should('eq', 200)
309
+ }
310
+ })
311
+ }
312
+
313
+ /**
314
+ * Login as Developer (core system user)
315
+ * Session is cached and reused across tests
316
+ *
317
+ * Developer has:
318
+ * - Dev Zone access
319
+ * - Not team-based (no setupTeamContext needed)
320
+ */
321
+ export function loginAsDefaultDeveloper() {
322
+ cy.session('default-developer-session', () => {
323
+ apiLogin(CORE_USERS.DEVELOPER, CORE_PASSWORD).then((apiLoginSucceeded) => {
324
+ if (apiLoginSucceeded) {
325
+ cy.visit('/devtools', { timeout: 60000 })
326
+ }
327
+ // Developer should land on devtools or dashboard
328
+ cy.url().should('satisfy', (url: string) => {
329
+ return url.includes('/devtools') || url.includes('/dashboard')
330
+ })
331
+ })
332
+ }, {
333
+ validate: () => {
334
+ cy.request({
335
+ url: '/api/auth/get-session',
336
+ timeout: API_TIMEOUT,
337
+ failOnStatusCode: false
338
+ }).its('status').should('eq', 200)
339
+ }
340
+ })
341
+ }
342
+
343
+ // Aliases for convenience
344
+ export const loginAsOwner = loginAsDefaultOwner
345
+ export const loginAsMember = loginAsDefaultMember
346
+ export const loginAsAdmin = loginAsDefaultAdmin
347
+ export const loginAsEditor = loginAsDefaultEditor
348
+ export const loginAsViewer = loginAsDefaultViewer
349
+ export const loginAsSuperadmin = loginAsDefaultSuperadmin
350
+ export const loginAsDeveloper = loginAsDefaultDeveloper
351
+
352
+ /**
353
+ * Returns theme users for backwards compatibility with external helper pattern.
354
+ * Used by tests that import { getThemeUsers } from session-helpers.
355
+ */
356
+ export function getThemeUsers() {
357
+ return DEFAULT_THEME_USERS
358
+ }
359
+
360
+ // ============================================================
361
+ // BILLING TEST USERS
362
+ // ============================================================
363
+
364
+ /**
365
+ * Billing Test Users - Teams with different subscription plans
366
+ *
367
+ * These users/teams are used to test billing features from different plan perspectives:
368
+ * - Free Plan: Carlos's personal team (team-personal-carlos-001)
369
+ * - Pro Plan: Everpoint Labs (team-everpoint-001)
370
+ * - Enterprise Plan: Ironvale Global (team-ironvale-002)
371
+ */
372
+ export const BILLING_TEAMS = {
373
+ FREE: {
374
+ teamId: 'team-personal-carlos-001',
375
+ name: 'Carlos Personal',
376
+ planSlug: 'free',
377
+ owner: 'carlos.mendoza@nextspark.dev'
378
+ },
379
+ PRO: {
380
+ teamId: 'team-everpoint-001',
381
+ name: 'Everpoint Labs',
382
+ planSlug: 'pro',
383
+ owner: 'carlos.mendoza@nextspark.dev'
384
+ },
385
+ ENTERPRISE: {
386
+ teamId: 'team-ironvale-002',
387
+ name: 'Ironvale Global',
388
+ planSlug: 'enterprise',
389
+ owner: 'ana.garcia@nextspark.dev'
390
+ }
391
+ } as const
392
+
393
+ /**
394
+ * Switch to a specific team after login
395
+ * @param teamId - Team ID to switch to
396
+ */
397
+ function switchToTeam(teamId: string) {
398
+ cy.window().then((win) => {
399
+ win.localStorage.setItem('activeTeamId', teamId)
400
+ })
401
+
402
+ cy.request({
403
+ method: 'POST',
404
+ url: '/api/v1/teams/switch',
405
+ body: { teamId },
406
+ timeout: API_TIMEOUT,
407
+ failOnStatusCode: false
408
+ })
409
+ }
410
+
411
+ /**
412
+ * Login as Carlos and switch to Free plan team
413
+ * Used for testing Free plan restrictions
414
+ */
415
+ export function loginAsFreePlanUser() {
416
+ cy.session('billing-free-plan-session', () => {
417
+ apiLogin(BILLING_TEAMS.FREE.owner).then((apiLoginSucceeded) => {
418
+ if (apiLoginSucceeded) {
419
+ cy.visit('/dashboard', { timeout: 60000, failOnStatusCode: false })
420
+ }
421
+ cy.url().should('include', '/dashboard')
422
+ // Switch to the Free team
423
+ switchToTeam(BILLING_TEAMS.FREE.teamId)
424
+ })
425
+ }, {
426
+ validate: () => {
427
+ cy.request({
428
+ url: '/api/auth/get-session',
429
+ timeout: API_TIMEOUT,
430
+ failOnStatusCode: false
431
+ }).its('status').should('eq', 200)
432
+
433
+ cy.window().then((win) => {
434
+ const teamId = win.localStorage.getItem('activeTeamId')
435
+ expect(teamId).to.eq(BILLING_TEAMS.FREE.teamId)
436
+ })
437
+ }
438
+ })
439
+ }
440
+
441
+ /**
442
+ * Login as Carlos and switch to Pro plan team (Everpoint)
443
+ * Used for testing Pro plan features
444
+ */
445
+ export function loginAsProPlanUser() {
446
+ cy.session('billing-pro-plan-session', () => {
447
+ apiLogin(BILLING_TEAMS.PRO.owner).then((apiLoginSucceeded) => {
448
+ if (apiLoginSucceeded) {
449
+ cy.visit('/dashboard', { timeout: 60000, failOnStatusCode: false })
450
+ }
451
+ cy.url().should('include', '/dashboard')
452
+ // Switch to the Pro team
453
+ switchToTeam(BILLING_TEAMS.PRO.teamId)
454
+ })
455
+ }, {
456
+ validate: () => {
457
+ cy.request({
458
+ url: '/api/auth/get-session',
459
+ timeout: API_TIMEOUT,
460
+ failOnStatusCode: false
461
+ }).its('status').should('eq', 200)
462
+
463
+ cy.window().then((win) => {
464
+ const teamId = win.localStorage.getItem('activeTeamId')
465
+ expect(teamId).to.eq(BILLING_TEAMS.PRO.teamId)
466
+ })
467
+ }
468
+ })
469
+ }
470
+
471
+ /**
472
+ * Login as Ana and switch to Enterprise plan team (Ironvale)
473
+ * Used for testing Enterprise plan features
474
+ */
475
+ export function loginAsEnterprisePlanUser() {
476
+ cy.session('billing-enterprise-plan-session', () => {
477
+ // Ana is owner of Ironvale
478
+ apiLogin(BILLING_TEAMS.ENTERPRISE.owner).then((apiLoginSucceeded) => {
479
+ if (apiLoginSucceeded) {
480
+ cy.visit('/dashboard', { timeout: 60000, failOnStatusCode: false })
481
+ }
482
+ cy.url().should('include', '/dashboard')
483
+ // Switch to the Enterprise team
484
+ switchToTeam(BILLING_TEAMS.ENTERPRISE.teamId)
485
+ })
486
+ }, {
487
+ validate: () => {
488
+ cy.request({
489
+ url: '/api/auth/get-session',
490
+ timeout: API_TIMEOUT,
491
+ failOnStatusCode: false
492
+ }).its('status').should('eq', 200)
493
+
494
+ cy.window().then((win) => {
495
+ const teamId = win.localStorage.getItem('activeTeamId')
496
+ expect(teamId).to.eq(BILLING_TEAMS.ENTERPRISE.teamId)
497
+ })
498
+ }
499
+ })
500
+ }