@nextsparkjs/theme-default 0.1.0-beta.20 → 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 (220) hide show
  1. package/package.json +1 -1
  2. package/tests/cypress/e2e/_devtools/access.bdd.md +262 -0
  3. package/tests/cypress/e2e/_devtools/access.cy.ts +171 -0
  4. package/tests/cypress/e2e/_devtools/navigation.bdd.md +261 -0
  5. package/tests/cypress/e2e/_devtools/navigation.cy.ts +157 -0
  6. package/tests/cypress/e2e/_devtools/pages.bdd.md +303 -0
  7. package/tests/cypress/e2e/_devtools/pages.cy.ts +184 -0
  8. package/tests/cypress/e2e/_docs/README.md +215 -0
  9. package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin-teams.narration.json +155 -0
  10. package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin.cy.ts +390 -0
  11. package/tests/cypress/e2e/_docs/tutorials/teams-system.doc.cy.ts +349 -0
  12. package/tests/cypress/e2e/_docs/tutorials/teams-system.narration.json +165 -0
  13. package/tests/cypress/e2e/_selectors/auth.cy.ts +306 -0
  14. package/tests/cypress/e2e/_selectors/billing.cy.ts +89 -0
  15. package/tests/cypress/e2e/_selectors/dashboard-mobile.cy.ts +113 -0
  16. package/tests/cypress/e2e/_selectors/dashboard-navigation.cy.ts +89 -0
  17. package/tests/cypress/e2e/_selectors/dashboard-sidebar.cy.ts +60 -0
  18. package/tests/cypress/e2e/_selectors/dashboard-topnav.cy.ts +146 -0
  19. package/tests/cypress/e2e/_selectors/devtools.cy.ts +210 -0
  20. package/tests/cypress/e2e/_selectors/global-search.cy.ts +88 -0
  21. package/tests/cypress/e2e/_selectors/pages-editor.cy.ts +179 -0
  22. package/tests/cypress/e2e/_selectors/posts-editor.cy.ts +282 -0
  23. package/tests/cypress/e2e/_selectors/public.cy.ts +112 -0
  24. package/tests/cypress/e2e/_selectors/settings-api-keys.cy.ts +228 -0
  25. package/tests/cypress/e2e/_selectors/settings-billing.cy.ts +105 -0
  26. package/tests/cypress/e2e/_selectors/settings-layout.cy.ts +119 -0
  27. package/tests/cypress/e2e/_selectors/settings-password.cy.ts +71 -0
  28. package/tests/cypress/e2e/_selectors/settings-profile.cy.ts +82 -0
  29. package/tests/cypress/e2e/_selectors/settings-teams.cy.ts +68 -0
  30. package/tests/cypress/e2e/_selectors/superadmin.cy.ts +185 -0
  31. package/tests/cypress/e2e/_selectors/tasks.cy.ts +242 -0
  32. package/tests/cypress/e2e/_selectors/taxonomies.cy.ts +126 -0
  33. package/tests/cypress/e2e/_selectors/teams.cy.ts +142 -0
  34. package/tests/cypress/e2e/_superadmin/all-teams.bdd.md +261 -0
  35. package/tests/cypress/e2e/_superadmin/all-teams.cy.ts +177 -0
  36. package/tests/cypress/e2e/_superadmin/all-users.bdd.md +406 -0
  37. package/tests/cypress/e2e/_superadmin/all-users.cy.ts +294 -0
  38. package/tests/cypress/e2e/_superadmin/dashboard.bdd.md +235 -0
  39. package/tests/cypress/e2e/_superadmin/dashboard.cy.ts +149 -0
  40. package/tests/cypress/e2e/_superadmin/subscriptions-overview.bdd.md +290 -0
  41. package/tests/cypress/e2e/_superadmin/subscriptions-overview.cy.ts +194 -0
  42. package/tests/cypress/e2e/ai/ai-usage.cy.ts +209 -0
  43. package/tests/cypress/e2e/ai/chat-api.cy.ts +107 -0
  44. package/tests/cypress/e2e/ai/guardrails.cy.ts +332 -0
  45. package/tests/cypress/e2e/api/billing/BillingAPIController.js +319 -0
  46. package/tests/cypress/e2e/api/billing/check-action.cy.ts +326 -0
  47. package/tests/cypress/e2e/api/billing/checkout.cy.ts +358 -0
  48. package/tests/cypress/e2e/api/billing/lifecycle.cy.ts +423 -0
  49. package/tests/cypress/e2e/api/billing/plans/README.md +345 -0
  50. package/tests/cypress/e2e/api/billing/plans/business.cy.ts +412 -0
  51. package/tests/cypress/e2e/api/billing/plans/downgrade.cy.ts +510 -0
  52. package/tests/cypress/e2e/api/billing/plans/fixtures/billing-plans.json +163 -0
  53. package/tests/cypress/e2e/api/billing/plans/free.cy.ts +500 -0
  54. package/tests/cypress/e2e/api/billing/plans/pro.cy.ts +497 -0
  55. package/tests/cypress/e2e/api/billing/plans/starter.cy.ts +342 -0
  56. package/tests/cypress/e2e/api/billing/portal.cy.ts +313 -0
  57. package/tests/cypress/e2e/api/devtools/registries.bdd.md +300 -0
  58. package/tests/cypress/e2e/api/devtools/registries.cy.ts +368 -0
  59. package/tests/cypress/e2e/api/entities/blocks-scope.cy.ts +396 -0
  60. package/tests/cypress/e2e/api/entities/customers-crud.cy.ts +648 -0
  61. package/tests/cypress/e2e/api/entities/customers-metas.cy.ts +839 -0
  62. package/tests/cypress/e2e/api/entities/pages-crud.cy.ts +425 -0
  63. package/tests/cypress/e2e/api/entities/pages-status.cy.ts +335 -0
  64. package/tests/cypress/e2e/api/entities/post-categories-crud.cy.ts +610 -0
  65. package/tests/cypress/e2e/api/entities/posts-crud.cy.ts +709 -0
  66. package/tests/cypress/e2e/api/entities/posts-status.cy.ts +396 -0
  67. package/tests/cypress/e2e/api/entities/tasks-crud.cy.ts +602 -0
  68. package/tests/cypress/e2e/api/entities/tasks-metas.cy.ts +878 -0
  69. package/tests/cypress/e2e/api/entities/users-crud.cy.ts +469 -0
  70. package/tests/cypress/e2e/api/entities/users-metas.cy.ts +913 -0
  71. package/tests/cypress/e2e/api/entities/users-security.cy.ts +375 -0
  72. package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.bdd.md +375 -0
  73. package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.cy.ts +346 -0
  74. package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.bdd.md +451 -0
  75. package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.cy.ts +447 -0
  76. package/tests/cypress/e2e/api/scheduled-actions/scheduling.bdd.md +649 -0
  77. package/tests/cypress/e2e/api/scheduled-actions/scheduling.cy.ts +333 -0
  78. package/tests/cypress/e2e/api/settings/api-keys.crud.cy.ts +923 -0
  79. package/tests/cypress/e2e/uat/auth/app-roles/developer-login.bdd.md +231 -0
  80. package/tests/cypress/e2e/uat/auth/app-roles/developer-login.cy.ts +144 -0
  81. package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.bdd.md +118 -0
  82. package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.cy.ts +84 -0
  83. package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.bdd.md +288 -0
  84. package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.cy.ts +188 -0
  85. package/tests/cypress/e2e/uat/auth/login-logout.bdd.md +160 -0
  86. package/tests/cypress/e2e/uat/auth/login-logout.cy.ts +116 -0
  87. package/tests/cypress/e2e/uat/auth/password-reset.bdd.md +289 -0
  88. package/tests/cypress/e2e/uat/auth/password-reset.cy.ts +200 -0
  89. package/tests/cypress/e2e/uat/auth/team-roles/admin-login.bdd.md +225 -0
  90. package/tests/cypress/e2e/uat/auth/team-roles/admin-login.cy.ts +148 -0
  91. package/tests/cypress/e2e/uat/auth/team-roles/member-login.bdd.md +251 -0
  92. package/tests/cypress/e2e/uat/auth/team-roles/member-login.cy.ts +163 -0
  93. package/tests/cypress/e2e/uat/auth/team-roles/owner-login.bdd.md +231 -0
  94. package/tests/cypress/e2e/uat/auth/team-roles/owner-login.cy.ts +141 -0
  95. package/tests/cypress/e2e/uat/billing/extended.bdd.md +273 -0
  96. package/tests/cypress/e2e/uat/billing/extended.cy.ts +209 -0
  97. package/tests/cypress/e2e/uat/billing/feature-gates.bdd.md +407 -0
  98. package/tests/cypress/e2e/uat/billing/feature-gates.cy.ts +307 -0
  99. package/tests/cypress/e2e/uat/billing/page.bdd.md +329 -0
  100. package/tests/cypress/e2e/uat/billing/page.cy.ts +250 -0
  101. package/tests/cypress/e2e/uat/billing/status.bdd.md +190 -0
  102. package/tests/cypress/e2e/uat/billing/status.cy.ts +145 -0
  103. package/tests/cypress/e2e/uat/billing/team-switch.bdd.md +156 -0
  104. package/tests/cypress/e2e/uat/billing/team-switch.cy.ts +122 -0
  105. package/tests/cypress/e2e/uat/billing/usage.bdd.md +218 -0
  106. package/tests/cypress/e2e/uat/billing/usage.cy.ts +176 -0
  107. package/tests/cypress/e2e/uat/blocks/hero.bdd.md +124 -0
  108. package/tests/cypress/e2e/uat/blocks/hero.cy.ts +56 -0
  109. package/tests/cypress/e2e/uat/devtools/api-tester.cy.ts +390 -0
  110. package/tests/cypress/e2e/uat/entities/customers/member.bdd.md +275 -0
  111. package/tests/cypress/e2e/uat/entities/customers/member.cy.ts +122 -0
  112. package/tests/cypress/e2e/uat/entities/customers/owner.bdd.md +243 -0
  113. package/tests/cypress/e2e/uat/entities/customers/owner.cy.ts +165 -0
  114. package/tests/cypress/e2e/uat/entities/pages/block-crud.bdd.md +476 -0
  115. package/tests/cypress/e2e/uat/entities/pages/block-crud.cy.ts +486 -0
  116. package/tests/cypress/e2e/uat/entities/pages/block-editor.bdd.md +460 -0
  117. package/tests/cypress/e2e/uat/entities/pages/block-editor.cy.ts +301 -0
  118. package/tests/cypress/e2e/uat/entities/pages/list.bdd.md +432 -0
  119. package/tests/cypress/e2e/uat/entities/pages/list.cy.ts +273 -0
  120. package/tests/cypress/e2e/uat/entities/pages/public-rendering.bdd.md +696 -0
  121. package/tests/cypress/e2e/uat/entities/pages/public-rendering.cy.ts +340 -0
  122. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.bdd.md +161 -0
  123. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.cy.ts +104 -0
  124. package/tests/cypress/e2e/uat/entities/posts/categories.bdd.md +375 -0
  125. package/tests/cypress/e2e/uat/entities/posts/categories.cy.ts +241 -0
  126. package/tests/cypress/e2e/uat/entities/posts/editor.bdd.md +429 -0
  127. package/tests/cypress/e2e/uat/entities/posts/editor.cy.ts +257 -0
  128. package/tests/cypress/e2e/uat/entities/posts/list.bdd.md +340 -0
  129. package/tests/cypress/e2e/uat/entities/posts/list.cy.ts +177 -0
  130. package/tests/cypress/e2e/uat/entities/posts/public.bdd.md +614 -0
  131. package/tests/cypress/e2e/uat/entities/posts/public.cy.ts +249 -0
  132. package/tests/cypress/e2e/uat/entities/tasks/member.bdd.md +222 -0
  133. package/tests/cypress/e2e/uat/entities/tasks/member.cy.ts +165 -0
  134. package/tests/cypress/e2e/uat/entities/tasks/owner.bdd.md +419 -0
  135. package/tests/cypress/e2e/uat/entities/tasks/owner.cy.ts +191 -0
  136. package/tests/cypress/e2e/uat/roles/editor-role.bdd.md +552 -0
  137. package/tests/cypress/e2e/uat/roles/editor-role.cy.ts +210 -0
  138. package/tests/cypress/e2e/uat/roles/member-restrictions.bdd.md +450 -0
  139. package/tests/cypress/e2e/uat/roles/member-restrictions.cy.ts +189 -0
  140. package/tests/cypress/e2e/uat/roles/owner-full-crud.bdd.md +530 -0
  141. package/tests/cypress/e2e/uat/roles/owner-full-crud.cy.ts +247 -0
  142. package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.bdd.md +736 -0
  143. package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.cy.ts +740 -0
  144. package/tests/cypress/e2e/uat/teams/roles-matrix.bdd.md +553 -0
  145. package/tests/cypress/e2e/uat/teams/roles-matrix.cy.ts +185 -0
  146. package/tests/cypress/e2e/uat/teams/switcher.bdd.md +1151 -0
  147. package/tests/cypress/e2e/uat/teams/switcher.cy.ts +497 -0
  148. package/tests/cypress/e2e/uat/teams/team-switcher.md +198 -0
  149. package/tests/cypress/fixtures/blocks.json +218 -0
  150. package/tests/cypress/fixtures/entities.json +78 -0
  151. package/tests/cypress/fixtures/page-builder.json +21 -0
  152. package/tests/cypress/src/components/CategoriesPOM.ts +382 -0
  153. package/tests/cypress/src/components/CustomersPOM.ts +439 -0
  154. package/tests/cypress/src/components/DevKeyringPOM.ts +160 -0
  155. package/tests/cypress/src/components/EntityForm.ts +375 -0
  156. package/tests/cypress/src/components/EntityList.ts +389 -0
  157. package/tests/cypress/src/components/PageBuilderPOM.ts +710 -0
  158. package/tests/cypress/src/components/PostEditorPOM.ts +370 -0
  159. package/tests/cypress/src/components/PostsListPOM.ts +223 -0
  160. package/tests/cypress/src/components/PublicPagePOM.ts +447 -0
  161. package/tests/cypress/src/components/PublicPostPOM.ts +146 -0
  162. package/tests/cypress/src/components/TasksPOM.ts +272 -0
  163. package/tests/cypress/src/components/TeamSwitcherPOM.ts +450 -0
  164. package/tests/cypress/src/components/index.ts +21 -0
  165. package/tests/cypress/src/controllers/ApiKeysAPIController.js +178 -0
  166. package/tests/cypress/src/controllers/BaseAPIController.js +317 -0
  167. package/tests/cypress/src/controllers/CustomerAPIController.js +251 -0
  168. package/tests/cypress/src/controllers/PagesAPIController.js +226 -0
  169. package/tests/cypress/src/controllers/PostsAPIController.js +250 -0
  170. package/tests/cypress/src/controllers/TaskAPIController.js +240 -0
  171. package/tests/cypress/src/controllers/UsersAPIController.js +242 -0
  172. package/tests/cypress/src/controllers/index.js +25 -0
  173. package/tests/cypress/src/core/AuthPOM.ts +450 -0
  174. package/tests/cypress/src/core/BasePOM.ts +86 -0
  175. package/tests/cypress/src/core/BlockEditorBasePOM.ts +576 -0
  176. package/tests/cypress/src/core/DashboardEntityPOM.ts +692 -0
  177. package/tests/cypress/src/core/index.ts +14 -0
  178. package/tests/cypress/src/entities/CustomersPOM.ts +172 -0
  179. package/tests/cypress/src/entities/PagesPOM.ts +137 -0
  180. package/tests/cypress/src/entities/PostsPOM.ts +137 -0
  181. package/tests/cypress/src/entities/TasksPOM.ts +176 -0
  182. package/tests/cypress/src/entities/index.ts +14 -0
  183. package/tests/cypress/src/features/BillingPOM.ts +385 -0
  184. package/tests/cypress/src/features/DashboardPOM.ts +245 -0
  185. package/tests/cypress/src/features/DevtoolsPOM.ts +739 -0
  186. package/tests/cypress/src/features/PageBuilderPOM.ts +263 -0
  187. package/tests/cypress/src/features/PostEditorPOM.ts +313 -0
  188. package/tests/cypress/src/features/ScheduledActionsPOM.ts +463 -0
  189. package/tests/cypress/src/features/SettingsPOM.ts +362 -0
  190. package/tests/cypress/src/features/SuperadminPOM.ts +331 -0
  191. package/tests/cypress/src/features/SuperadminTeamRolesPOM.ts +285 -0
  192. package/tests/cypress/src/features/index.ts +28 -0
  193. package/tests/cypress/src/helpers/ApiInterceptor.ts +177 -0
  194. package/tests/cypress/src/index.ts +101 -0
  195. package/tests/cypress/src/pages/dashboard/Dashboard.js +677 -0
  196. package/tests/cypress/src/pages/dashboard/DashboardPage.js +43 -0
  197. package/tests/cypress/src/pages/dashboard/DashboardStats.js +546 -0
  198. package/tests/cypress/src/pages/dashboard/index.js +6 -0
  199. package/tests/cypress/src/pages/index.js +5 -0
  200. package/tests/cypress/src/pages/public/FeaturesPage.js +28 -0
  201. package/tests/cypress/src/pages/public/LandingPage.js +69 -0
  202. package/tests/cypress/src/pages/public/PricingPage.js +33 -0
  203. package/tests/cypress/src/pages/public/index.js +6 -0
  204. package/tests/cypress/src/selectors.ts +46 -0
  205. package/tests/cypress/src/session-helpers.ts +500 -0
  206. package/tests/cypress/support/doc-commands.ts +260 -0
  207. package/tests/cypress.config.ts +150 -0
  208. package/tests/jest/components/post-header.test.tsx +377 -0
  209. package/tests/jest/config/role-config.test.ts +529 -0
  210. package/tests/jest/jest.config.ts +81 -0
  211. package/tests/jest/langchain/COVERAGE.md +372 -0
  212. package/tests/jest/langchain/guardrails.test.ts +465 -0
  213. package/tests/jest/langchain/streaming.test.ts +367 -0
  214. package/tests/jest/langchain/token-tracker.test.ts +455 -0
  215. package/tests/jest/langchain/tracer-callbacks.test.ts +881 -0
  216. package/tests/jest/langchain/tracer.test.ts +823 -0
  217. package/tests/jest/user-roles/role-helpers.test.ts +432 -0
  218. package/tests/jest/validation/categories.test.ts +429 -0
  219. package/tests/jest/validation/posts.test.ts +546 -0
  220. package/tests/tsconfig.json +15 -0
@@ -0,0 +1,497 @@
1
+ /// <reference types="cypress" />
2
+
3
+ /**
4
+ * Team Switcher - E2E Tests
5
+ *
6
+ * Tests for the TeamSwitcherCompact component in multi-tenant mode.
7
+ * Validates team switching functionality, data reload, and permission changes.
8
+ *
9
+ * @see team-switcher.md for documentation
10
+ */
11
+
12
+ import * as allure from 'allure-cypress'
13
+
14
+ import { DevKeyringPOM } from '../../../src/components/DevKeyringPOM'
15
+ import { TeamSwitcherPOM } from '../../../src/components/TeamSwitcherPOM'
16
+
17
+ // Test users from default theme
18
+ // Note: DB data shows different team counts than DevKeyring config comments
19
+ const USERS = {
20
+ // Multi-team users (actual DB data)
21
+ CARLOS: 'carlos.mendoza@nextspark.dev', // Everpoint (owner), Riverstone (member), Ironvale (admin) - 3 teams
22
+ SOFIA: 'sofia.lopez@nextspark.dev', // Riverstone (owner), Ironvale (admin) - 2 teams
23
+ EMILY: 'emily.johnson@nextspark.dev', // Everpoint (member), Riverstone (admin) - 2 teams
24
+ JAMES: 'james.wilson@nextspark.dev', // Everpoint (admin), Riverstone (member?) - 2 teams
25
+
26
+ // Single-team users
27
+ ANA: 'ana.garcia@nextspark.dev', // Ironvale Global (owner) - 1 team
28
+ SARAH: 'sarah.davis@nextspark.dev', // Ironvale Global (viewer) - 1 team
29
+ }
30
+
31
+ // Team slugs (from actual DB data visible in tests)
32
+ const TEAMS = {
33
+ EVERPOINT: 'everpoint-labs',
34
+ RIVERSTONE: 'riverstone-ventures',
35
+ // Note: Carlos also has "Carlos Mendoza Team" but slug unknown
36
+ // Ana may have different teams than expected
37
+ }
38
+
39
+ // Role translations (Spanish - app default locale is 'es')
40
+ const ROLES = {
41
+ OWNER: 'Propietario',
42
+ ADMIN: 'Administrador',
43
+ MEMBER: 'Miembro',
44
+ EDITOR: 'Editor',
45
+ VIEWER: 'Observador',
46
+ }
47
+
48
+ describe('Team Switcher', {
49
+ tags: ['@uat', '@feat-teams', '@workflow', '@regression']
50
+ }, () => {
51
+ const teamSwitcher = TeamSwitcherPOM.create()
52
+
53
+ beforeEach(() => {
54
+ allure.epic('UAT')
55
+ allure.feature('Teams')
56
+ allure.story('Team Switching')
57
+ })
58
+
59
+ // ============================================
60
+ // Single/Few Team User
61
+ // ============================================
62
+
63
+ describe('Single/Few Team User', () => {
64
+ beforeEach(() => {
65
+ // Use Ana - should have fewer teams than Carlos
66
+ cy.session('ana-few-teams', () => {
67
+ cy.visit('/login')
68
+ const devKeyring = DevKeyringPOM.create()
69
+ devKeyring.validateVisible()
70
+ devKeyring.quickLoginByEmail(USERS.ANA)
71
+ cy.url().should('include', '/dashboard')
72
+ })
73
+ cy.visit('/dashboard', { timeout: 60000, failOnStatusCode: false })
74
+ cy.url().should('include', '/dashboard')
75
+ })
76
+
77
+ it('TEAM_SW_001: User sees their teams in switcher', { tags: '@smoke' }, () => {
78
+ allure.severity('critical')
79
+ teamSwitcher.validateSwitcherVisible()
80
+ // Ana has at least 1 team (actual count from DB may vary)
81
+ teamSwitcher.open()
82
+ cy.get('[data-cy^="team-option-"]').should('have.length.at.least', 1)
83
+ })
84
+
85
+ it('TEAM_SW_002: Manage Teams link is visible and navigates correctly', () => {
86
+ teamSwitcher.validateManageTeamsVisible()
87
+ teamSwitcher.goToManageTeams()
88
+ })
89
+ })
90
+
91
+ // ============================================
92
+ // Multi-Team User
93
+ // ============================================
94
+
95
+ describe('Multi-Team User', () => {
96
+ beforeEach(() => {
97
+ cy.session('carlos-multi-team', () => {
98
+ cy.visit('/login')
99
+ const devKeyring = DevKeyringPOM.create()
100
+ devKeyring.validateVisible()
101
+ devKeyring.quickLoginByEmail(USERS.CARLOS)
102
+ cy.url().should('include', '/dashboard')
103
+ })
104
+ cy.visit('/dashboard', { timeout: 60000, failOnStatusCode: false })
105
+ cy.url().should('include', '/dashboard')
106
+ })
107
+
108
+ it('TEAM_SW_010: Multi-team user sees all teams', () => {
109
+ teamSwitcher.validateSwitcherVisible()
110
+ // Carlos has 3 teams: Everpoint Labs (owner), Carlos Mendoza Team (owner), Riverstone Ventures (member)
111
+ teamSwitcher.open()
112
+ cy.get('[data-cy^="team-option-"]').should('have.length.at.least', 2)
113
+ teamSwitcher.validateTeamInList(TEAMS.EVERPOINT)
114
+ teamSwitcher.validateTeamInList(TEAMS.RIVERSTONE)
115
+ })
116
+
117
+ it('TEAM_SW_011: Current team shows checkmark', () => {
118
+ // Carlos defaults to Everpoint Labs (owner)
119
+ // Validate checkmark for active team
120
+ teamSwitcher.validateTeamHasCheckmark(TEAMS.EVERPOINT)
121
+ // Close dropdown using escape key to avoid pointer-events issue
122
+ cy.get('body').type('{esc}')
123
+ cy.get(teamSwitcher.selectors.dropdown).should('not.exist')
124
+ // Now verify no checkmark on non-active team
125
+ teamSwitcher.validateTeamNoCheckmark(TEAMS.RIVERSTONE)
126
+ })
127
+
128
+ it('TEAM_SW_012: Roles are displayed correctly', () => {
129
+ // Carlos is owner in Everpoint, member in Riverstone (roles in Spanish)
130
+ teamSwitcher.validateRoleDisplayed(TEAMS.EVERPOINT, ROLES.OWNER)
131
+ // Close dropdown using escape key to avoid pointer-events issue
132
+ cy.get('body').type('{esc}')
133
+ cy.get(teamSwitcher.selectors.dropdown).should('not.exist')
134
+ teamSwitcher.validateRoleDisplayed(TEAMS.RIVERSTONE, ROLES.MEMBER)
135
+ })
136
+
137
+ it('TEAM_SW_013: Can switch teams successfully', () => {
138
+ // Validate starting on Everpoint
139
+ teamSwitcher.validateCurrentTeamName('Everpoint Labs')
140
+
141
+ // Switch to Riverstone
142
+ teamSwitcher.switchToTeam(TEAMS.RIVERSTONE)
143
+
144
+ // After reload, should be on Riverstone
145
+ teamSwitcher.validateCurrentTeamName('Riverstone Ventures')
146
+ })
147
+
148
+ it('TEAM_SW_014: Modal appears during switch', () => {
149
+ // Start switch process (don't wait for completion)
150
+ teamSwitcher.selectTeam(TEAMS.RIVERSTONE)
151
+
152
+ // Modal should appear
153
+ teamSwitcher.validateSwitchModalVisible()
154
+ })
155
+ })
156
+
157
+ // ============================================
158
+ // Data Reload After Switch
159
+ // ============================================
160
+
161
+ describe('Data Reload After Switch', () => {
162
+ beforeEach(() => {
163
+ cy.session('carlos-data-reload', () => {
164
+ cy.visit('/login')
165
+ const devKeyring = DevKeyringPOM.create()
166
+ devKeyring.validateVisible()
167
+ devKeyring.quickLoginByEmail(USERS.CARLOS)
168
+ cy.url().should('include', '/dashboard')
169
+ })
170
+ cy.visit('/dashboard', { timeout: 60000, failOnStatusCode: false })
171
+ cy.url().should('include', '/dashboard')
172
+ })
173
+
174
+ it('TEAM_SW_020: Customers reload after switch', () => {
175
+ // Navigate to customers
176
+ cy.visit('/dashboard/customers')
177
+ cy.get('[data-cy="customers-page"]', { timeout: 15000 }).should('exist')
178
+
179
+ // Store current URL team context (via header/team name in switcher)
180
+ teamSwitcher.getCurrentTeamName().then(originalTeam => {
181
+ // Switch to Riverstone
182
+ teamSwitcher.switchToTeam(TEAMS.RIVERSTONE)
183
+
184
+ // Navigate to customers again
185
+ cy.visit('/dashboard/customers')
186
+ cy.get('[data-cy="customers-page"]', { timeout: 15000 }).should('exist')
187
+
188
+ // Team name should be different
189
+ teamSwitcher.getCurrentTeamName().should('not.eq', originalTeam)
190
+ })
191
+ })
192
+
193
+ it('TEAM_SW_021: Tasks reload after switch', () => {
194
+ // Navigate to tasks
195
+ cy.visit('/dashboard/tasks')
196
+ cy.get('[data-cy="tasks-page"]', { timeout: 15000 }).should('exist')
197
+
198
+ // Store current team context
199
+ teamSwitcher.getCurrentTeamName().then(originalTeam => {
200
+ // Switch to Riverstone
201
+ teamSwitcher.switchToTeam(TEAMS.RIVERSTONE)
202
+
203
+ // Navigate to tasks again
204
+ cy.visit('/dashboard/tasks')
205
+ cy.get('[data-cy="tasks-page"]', { timeout: 15000 }).should('exist')
206
+
207
+ // Team name should be different
208
+ teamSwitcher.getCurrentTeamName().should('not.eq', originalTeam)
209
+ })
210
+ })
211
+
212
+ it('TEAM_SW_022: Sidebar shows new team name', () => {
213
+ // Validate starting on Everpoint
214
+ teamSwitcher.validateCurrentTeamName('Everpoint Labs')
215
+
216
+ // Switch to Riverstone
217
+ teamSwitcher.switchToTeam(TEAMS.RIVERSTONE)
218
+
219
+ // Sidebar should show Riverstone
220
+ teamSwitcher.validateCurrentTeamName('Riverstone Ventures')
221
+ })
222
+ })
223
+
224
+ // ============================================
225
+ // Permission Changes
226
+ // ============================================
227
+
228
+ describe('Permission Changes', () => {
229
+ // Use fresh session for each test to avoid state issues
230
+ beforeEach(() => {
231
+ cy.session('carlos-permissions', () => {
232
+ cy.visit('/login')
233
+ const devKeyring = DevKeyringPOM.create()
234
+ devKeyring.validateVisible()
235
+ devKeyring.quickLoginByEmail(USERS.CARLOS)
236
+ cy.url().should('include', '/dashboard')
237
+ })
238
+ cy.visit('/dashboard', { timeout: 60000, failOnStatusCode: false })
239
+ cy.url().should('include', '/dashboard')
240
+ })
241
+
242
+ it('TEAM_SW_030: Owner can create customer', () => {
243
+ // Carlos is owner in Everpoint
244
+ cy.visit('/dashboard/customers')
245
+ cy.get('[data-cy="customers-page"]', { timeout: 15000 }).should('exist')
246
+
247
+ // Create button should be visible for owner (uses entity-slug-add pattern)
248
+ cy.get('[data-cy="customers-add"]').should('be.visible')
249
+ })
250
+
251
+ it('TEAM_SW_031: Owner can edit/delete customer via detail page', () => {
252
+ // Owner can access edit/delete on detail page
253
+ cy.visit('/dashboard/customers')
254
+ cy.get('[data-cy="customers-page"]', { timeout: 15000 }).should('exist')
255
+
256
+ // Click on the first row to go to detail (row is clickable)
257
+ cy.get('[data-cy^="customers-row-"]').first().click()
258
+
259
+ // Wait for detail page to load
260
+ cy.url().should('match', /\/dashboard\/customers\/[^/]+$/)
261
+
262
+ // On detail page, edit and delete buttons should be visible for owner
263
+ cy.get('[data-cy="customers-edit-btn"]').should('be.visible')
264
+ cy.get('[data-cy="customers-delete-btn"]').should('be.visible')
265
+ })
266
+
267
+ it('TEAM_SW_032: Member cannot create customer', () => {
268
+ // Switch to Riverstone where Carlos is member
269
+ teamSwitcher.switchToTeam(TEAMS.RIVERSTONE)
270
+
271
+ cy.visit('/dashboard/customers')
272
+ cy.get('[data-cy="customers-page"]', { timeout: 15000 }).should('exist')
273
+
274
+ // Create button should NOT be visible for member
275
+ cy.get('[data-cy="customers-add"]').should('not.exist')
276
+ })
277
+
278
+ it('TEAM_SW_033: Member cannot delete customer via detail page', () => {
279
+ // Switch to Riverstone where Carlos is member
280
+ teamSwitcher.switchToTeam(TEAMS.RIVERSTONE)
281
+
282
+ cy.visit('/dashboard/customers')
283
+ cy.get('[data-cy="customers-page"]', { timeout: 15000 }).should('exist')
284
+
285
+ // Click on first customer row to go to detail
286
+ cy.get('[data-cy^="customers-row-"]').first().click()
287
+
288
+ // Delete button should NOT be visible for member
289
+ cy.get('[data-cy="customers-delete"]').should('not.exist')
290
+ })
291
+
292
+ it('TEAM_SW_034: Member blocked from /edit URL', () => {
293
+ // Switch to Riverstone where Carlos is member
294
+ teamSwitcher.switchToTeam(TEAMS.RIVERSTONE)
295
+
296
+ // Get a customer ID from the list
297
+ cy.visit('/dashboard/customers')
298
+ cy.get('[data-cy="customers-page"]', { timeout: 15000 }).should('exist')
299
+
300
+ cy.get('[data-cy^="customers-row-"]').first().invoke('attr', 'data-cy').then((dataCy) => {
301
+ const customerId = dataCy?.replace('customers-row-', '')
302
+
303
+ // Try direct URL access to edit
304
+ cy.visit(`/dashboard/customers/${customerId}/edit`)
305
+
306
+ // Should show permission denied
307
+ teamSwitcher.validatePermissionDenied()
308
+ })
309
+ })
310
+
311
+ it('TEAM_SW_035: Member cannot access create URL or lacks create permissions', () => {
312
+ // Switch to Riverstone where Carlos is member
313
+ teamSwitcher.switchToTeam(TEAMS.RIVERSTONE)
314
+
315
+ // Member should not be able to create customers
316
+ // This is validated by checking the customers list page does NOT have create button
317
+ cy.visit('/dashboard/customers')
318
+ cy.get('[data-cy="customers-page"]', { timeout: 15000 }).should('exist')
319
+
320
+ // The create button should NOT be visible for member (most reliable check)
321
+ cy.get('[data-cy="customers-add"]').should('not.exist')
322
+ })
323
+ })
324
+
325
+ // ============================================
326
+ // UI Behavior
327
+ // ============================================
328
+
329
+ describe('UI Behavior', () => {
330
+ beforeEach(() => {
331
+ cy.session('carlos-ui-behavior', () => {
332
+ cy.visit('/login')
333
+ const devKeyring = DevKeyringPOM.create()
334
+ devKeyring.validateVisible()
335
+ devKeyring.quickLoginByEmail(USERS.CARLOS)
336
+ cy.url().should('include', '/dashboard')
337
+ })
338
+ cy.visit('/dashboard', { timeout: 60000, failOnStatusCode: false })
339
+ cy.url().should('include', '/dashboard')
340
+ })
341
+
342
+ it('TEAM_SW_040: Dropdown closes on escape key', () => {
343
+ // Open dropdown
344
+ teamSwitcher.open()
345
+
346
+ // Click outside using Escape key (more reliable than body click with radix modals)
347
+ cy.get('body').type('{esc}')
348
+
349
+ // Dropdown should close
350
+ cy.get(teamSwitcher.selectors.dropdown).should('not.exist')
351
+ })
352
+
353
+ it('TEAM_SW_041: Team options display team names', () => {
354
+ // Open dropdown and verify team names are displayed
355
+ teamSwitcher.open()
356
+ cy.get(`[data-cy="team-option-${TEAMS.EVERPOINT}"]`).should('contain', 'Everpoint')
357
+ cy.get(`[data-cy="team-option-${TEAMS.RIVERSTONE}"]`).should('contain', 'Riverstone')
358
+ })
359
+
360
+ it('TEAM_SW_042: Switcher hidden when sidebar collapsed', () => {
361
+ // First expand sidebar (ensureSidebarExpanded handles if already collapsed)
362
+ teamSwitcher.ensureSidebarExpanded()
363
+
364
+ // Validate sidebar is expanded and switcher visible
365
+ teamSwitcher.validateSidebarExpanded()
366
+ cy.get(teamSwitcher.selectors.trigger).should('be.visible')
367
+
368
+ // Collapse sidebar using toggle button in TopNavbar
369
+ teamSwitcher.collapseSidebar()
370
+
371
+ // Team switcher should not be visible when collapsed
372
+ teamSwitcher.validateSwitcherNotVisible()
373
+
374
+ // Expand sidebar again
375
+ teamSwitcher.expandSidebar()
376
+
377
+ // Team switcher should be visible again
378
+ cy.get(teamSwitcher.selectors.trigger).should('be.visible')
379
+ })
380
+ })
381
+
382
+ // ============================================
383
+ // Viewer Role
384
+ // ============================================
385
+
386
+ describe('Viewer Role', () => {
387
+ beforeEach(() => {
388
+ cy.session('sarah-viewer', () => {
389
+ cy.visit('/login')
390
+ const devKeyring = DevKeyringPOM.create()
391
+ devKeyring.validateVisible()
392
+ devKeyring.quickLoginByEmail(USERS.SARAH)
393
+ cy.url().should('include', '/dashboard')
394
+ })
395
+ cy.visit('/dashboard', { timeout: 60000, failOnStatusCode: false })
396
+ cy.url().should('include', '/dashboard')
397
+ })
398
+
399
+ it('TEAM_SW_043: Viewer can only read customers', () => {
400
+ // Sarah is a viewer in Ironvale - navigate to customers
401
+ cy.visit('/dashboard/customers')
402
+ // Viewer should see the list (even if empty) or be redirected
403
+ cy.url().should('include', '/dashboard')
404
+ // If list is visible, viewer should NOT have create button
405
+ cy.get('body').then($body => {
406
+ if ($body.find('[data-cy="customers-page"]').length > 0) {
407
+ cy.get('[data-cy="customers-add"]').should('not.exist')
408
+ }
409
+ // Otherwise, access was denied which is also valid for viewer
410
+ })
411
+ })
412
+
413
+ it('TEAM_SW_044: Viewer can only read tasks', () => {
414
+ // Sarah is a viewer in Ironvale - navigate to tasks
415
+ cy.visit('/dashboard/tasks')
416
+ // Viewer should see the list (even if empty) or be redirected
417
+ cy.url().should('include', '/dashboard')
418
+ // If list is visible, viewer should NOT have create button
419
+ cy.get('body').then($body => {
420
+ if ($body.find('[data-cy="tasks-page"]').length > 0) {
421
+ cy.get('[data-cy="tasks-add"]').should('not.exist')
422
+ }
423
+ // Otherwise, access was denied which is also valid for viewer
424
+ })
425
+ })
426
+
427
+ it('TEAM_SW_045: Viewer blocked from create URL', () => {
428
+ // Viewer should be blocked from create pages
429
+ cy.visit('/dashboard/customers/create')
430
+ // Either shows permission denied or redirects away from create
431
+ cy.get('body').then($body => {
432
+ if ($body.find('[data-cy="permission-denied"]').length > 0) {
433
+ cy.get('[data-cy="permission-denied"]').should('be.visible')
434
+ } else {
435
+ // If not permission denied page, should have been redirected (not on create form)
436
+ cy.get('[data-cy="customers-form"]').should('not.exist')
437
+ cy.log('✅ Viewer was redirected or blocked from create form')
438
+ }
439
+ })
440
+ })
441
+ })
442
+
443
+ // ============================================
444
+ // Mobile
445
+ // ============================================
446
+
447
+ describe('Mobile', () => {
448
+ beforeEach(() => {
449
+ // Set mobile viewport
450
+ cy.viewport('iphone-x')
451
+
452
+ cy.session('carlos-mobile', () => {
453
+ cy.visit('/login')
454
+ const devKeyring = DevKeyringPOM.create()
455
+ devKeyring.validateVisible()
456
+ devKeyring.quickLoginByEmail(USERS.CARLOS)
457
+ cy.url().should('include', '/dashboard')
458
+ })
459
+ cy.visit('/dashboard', { timeout: 60000, failOnStatusCode: false })
460
+ cy.url().should('include', '/dashboard')
461
+ })
462
+
463
+ it('TEAM_SW_050: Switcher visible in MobileMoreSheet', () => {
464
+ // Open mobile more menu using POM method
465
+ teamSwitcher.openMobileMoreSheet()
466
+
467
+ // Team switcher should be visible in the sheet
468
+ teamSwitcher.validateMobileTeamSwitcherVisible()
469
+ })
470
+
471
+ it('TEAM_SW_051: Can switch teams from mobile', () => {
472
+ // Switch teams using mobile POM method
473
+ teamSwitcher.switchToTeamMobile(TEAMS.RIVERSTONE)
474
+
475
+ // Wait for page to stabilize after switch (modal closes, page reloads)
476
+ cy.wait(1000)
477
+
478
+ // Verify switch happened by checking team name in the trigger
479
+ // After reload on mobile, use force click if needed
480
+ cy.get(teamSwitcher.selectors.mobileMoreButton, { timeout: 10000 }).click({ force: true })
481
+ cy.get(teamSwitcher.selectors.mobileMoreSheet, { timeout: 5000 }).should('be.visible')
482
+ cy.get(teamSwitcher.selectors.trigger).should('contain', 'Riverstone')
483
+ })
484
+
485
+ it('TEAM_SW_052: Mobile shows all teams', () => {
486
+ // Open mobile more menu
487
+ teamSwitcher.openMobileMoreSheet()
488
+
489
+ // Open team switcher dropdown from mobile sheet
490
+ teamSwitcher.openMobileTeamDropdown()
491
+
492
+ // All teams should be visible in dropdown
493
+ cy.get(`[data-cy="team-option-${TEAMS.EVERPOINT}"]`).should('be.visible')
494
+ cy.get(`[data-cy="team-option-${TEAMS.RIVERSTONE}"]`).should('be.visible')
495
+ })
496
+ })
497
+ })
@@ -0,0 +1,198 @@
1
+ # Team Switcher - E2E Tests
2
+
3
+ ## Overview
4
+
5
+ Comprehensive test suite for the TeamSwitcherCompact component in multi-tenant mode.
6
+ Tests team switching functionality, data reload after switch, and permission changes.
7
+
8
+ **Test File:** `team-switcher.cy.ts`
9
+
10
+ ## Component Characteristics
11
+
12
+ | Property | Value |
13
+ |----------|-------|
14
+ | **Component** | `TeamSwitcherCompact` |
15
+ | **Location** | Sidebar footer, MobileMoreSheet |
16
+ | **Mode** | Multi-tenant |
17
+ | **Modal** | `TeamSwitchModal` (~1.4s animation) |
18
+
19
+ ## Test Users
20
+
21
+ | User | Email | Teams | Roles |
22
+ |------|-------|-------|-------|
23
+ | Carlos | carlos.mendoza@nextspark.dev | Everpoint Labs, Carlos Mendoza Team, Riverstone Ventures | owner, owner, member |
24
+ | Ana | ana.garcia@nextspark.dev | Multiple teams | owner |
25
+ | Sarah | sarah.davis@nextspark.dev | Ironvale Global | viewer |
26
+
27
+ **Password for all:** `Test1234`
28
+
29
+ ## Test Coverage
30
+
31
+ ### 1. Single/Few Team User (2 tests)
32
+
33
+ | ID | Test Case | Status |
34
+ |----|-----------|--------|
35
+ | TEAM_SW_001 | User sees their teams in switcher | Passing |
36
+ | TEAM_SW_002 | Manage Teams link visible and navigates | Passing |
37
+
38
+ ### 2. Multi-Team User (5 tests)
39
+
40
+ | ID | Test Case | Status |
41
+ |----|-----------|--------|
42
+ | TEAM_SW_010 | Multi-team user sees all teams | Passing |
43
+ | TEAM_SW_011 | Current team shows checkmark | Passing |
44
+ | TEAM_SW_012 | Roles displayed correctly (Spanish) | Passing |
45
+ | TEAM_SW_013 | Can switch teams successfully | Passing |
46
+ | TEAM_SW_014 | Modal appears during switch | Passing |
47
+
48
+ ### 3. Data Reload After Switch (3 tests)
49
+
50
+ | ID | Test Case | Status |
51
+ |----|-----------|--------|
52
+ | TEAM_SW_020 | Customers reload after switch | Passing |
53
+ | TEAM_SW_021 | Tasks reload after switch | Passing |
54
+ | TEAM_SW_022 | Sidebar shows new team name | Passing |
55
+
56
+ ### 4. Permission Changes (6 tests)
57
+
58
+ | ID | Test Case | Status |
59
+ |----|-----------|--------|
60
+ | TEAM_SW_030 | Owner can create customer | Passing |
61
+ | TEAM_SW_031 | Owner can edit/delete customer | Pending (missing data-cy) |
62
+ | TEAM_SW_032 | Member cannot create customer | Passing |
63
+ | TEAM_SW_033 | Member cannot delete customer | Pending (missing data-cy) |
64
+ | TEAM_SW_034 | Member blocked from /edit URL | Pending (missing data-cy) |
65
+ | TEAM_SW_035 | Member blocked from /create URL | Pending (missing data-cy) |
66
+
67
+ ### 5. UI Behavior (3 tests)
68
+
69
+ | ID | Test Case | Status |
70
+ |----|-----------|--------|
71
+ | TEAM_SW_040 | Dropdown closes on escape key | Passing |
72
+ | TEAM_SW_041 | Team avatar/initials display | Pending (selector needs work) |
73
+ | TEAM_SW_042 | Switcher hidden when sidebar collapsed | Pending (missing data-cy) |
74
+
75
+ ### 6. Viewer Role (3 tests)
76
+
77
+ | ID | Test Case | Status |
78
+ |----|-----------|--------|
79
+ | TEAM_SW_043 | Viewer can only read customers | Pending (Ironvale no entities) |
80
+ | TEAM_SW_044 | Viewer can only read tasks | Pending (Ironvale no entities) |
81
+ | TEAM_SW_045 | Viewer blocked from action URLs | Pending (missing data-cy) |
82
+
83
+ ### 7. Mobile (3 tests)
84
+
85
+ | ID | Test Case | Status |
86
+ |----|-----------|--------|
87
+ | TEAM_SW_050 | Switcher visible in MobileMoreSheet | Pending (missing data-cy) |
88
+ | TEAM_SW_051 | Can switch teams from mobile | Pending (missing data-cy) |
89
+ | TEAM_SW_052 | Mobile shows all teams | Pending (missing data-cy) |
90
+
91
+ ## Summary
92
+
93
+ | Category | Total | Passing | Pending |
94
+ |----------|-------|---------|---------|
95
+ | Single/Few Team User | 2 | 2 | 0 |
96
+ | Multi-Team User | 5 | 5 | 0 |
97
+ | Data Reload | 3 | 3 | 0 |
98
+ | Permission Changes | 6 | 2 | 4 |
99
+ | UI Behavior | 3 | 1 | 2 |
100
+ | Viewer Role | 3 | 0 | 3 |
101
+ | Mobile | 3 | 0 | 3 |
102
+ | **Total** | **25** | **13** | **12** |
103
+
104
+ ## Data-cy Selectors Used
105
+
106
+ ### TeamSwitcherCompact
107
+ ```
108
+ [data-cy="team-switcher-compact"] - Trigger button
109
+ [data-cy="team-switcher-dropdown"] - Dropdown container
110
+ [data-cy="team-option-{slug}"] - Team options (dynamic)
111
+ [data-cy="manage-teams-link"] - Manage Teams navigation
112
+ ```
113
+
114
+ ### TeamSwitchModal
115
+ ```
116
+ [data-cy="team-switch-modal"] - Modal container
117
+ ```
118
+
119
+ ### Entity Lists
120
+ ```
121
+ [data-cy="customers-list"] - Customers list container
122
+ [data-cy="customers-row-{id}"] - Customer row (dynamic)
123
+ [data-cy="customers-add"] - Add customer button
124
+ [data-cy="tasks-list"] - Tasks list container
125
+ [data-cy="tasks-row-{id}"] - Task row (dynamic)
126
+ [data-cy="tasks-add"] - Add task button
127
+ ```
128
+
129
+ ## Missing Data-cy Attributes (TODOs)
130
+
131
+ To enable pending tests, add these attributes:
132
+
133
+ | Component | Attribute | Description |
134
+ |-----------|-----------|-------------|
135
+ | EntityList row | `data-cy="entity-row-actions"` | Actions menu trigger |
136
+ | Action menu | `data-cy="action-edit"` | Edit action |
137
+ | Action menu | `data-cy="action-delete"` | Delete action |
138
+ | NoPermission | `data-cy="permission-denied"` | Permission denied message |
139
+ | MobileMoreSheet | `data-cy="mobile-more-button"` | Mobile more button |
140
+ | Sidebar | `data-cy="sidebar-collapse"` | Collapse button |
141
+ | Sidebar | `data-cy="sidebar-expand"` | Expand button |
142
+
143
+ ## POM Class
144
+
145
+ **Path:** `test/cypress/src/classes/components/teams/TeamSwitcher.js`
146
+
147
+ ### Key Methods
148
+
149
+ | Method | Description |
150
+ |--------|-------------|
151
+ | `validateSwitcherVisible()` | Assert switcher is visible |
152
+ | `open()` | Open dropdown (idempotent) |
153
+ | `validateTeamCount(n)` | Assert team count |
154
+ | `validateTeamInList(slug)` | Assert team exists |
155
+ | `validateTeamHasCheckmark(slug)` | Assert team is selected |
156
+ | `validateRoleDisplayed(slug, role)` | Assert role text |
157
+ | `switchToTeam(slug)` | Switch and wait for completion |
158
+ | `validateSwitchModalVisible()` | Assert modal during switch |
159
+ | `goToManageTeams()` | Navigate to team settings |
160
+
161
+ ## Role Translations (Spanish)
162
+
163
+ | English | Spanish |
164
+ |---------|---------|
165
+ | Owner | Propietario |
166
+ | Admin | Administrador |
167
+ | Member | Miembro |
168
+ | Viewer | Visualizador |
169
+
170
+ ## Key Behaviors
171
+
172
+ ### Team Switching Flow
173
+ 1. User clicks team in dropdown
174
+ 2. `TeamSwitchModal` appears (~1.4s animation)
175
+ 3. Progress bar fills
176
+ 4. Page reloads with new team context
177
+ 5. Data (customers/tasks) reloads for new team
178
+
179
+ ### Permission Enforcement
180
+ - **Owner**: Full CRUD access
181
+ - **Admin**: Create/Read/Update (no delete)
182
+ - **Member**: Read only (no create/edit/delete buttons)
183
+ - **Viewer**: Read only
184
+
185
+ ### Session Management
186
+ Tests use `cy.session()` for performance:
187
+ - `ana-few-teams` - Single/few team user session
188
+ - `carlos-multi-team` - Multi-team user session
189
+ - `carlos-data-reload` - Data reload tests session
190
+ - `carlos-permissions` - Permission tests session
191
+ - `carlos-ui-behavior` - UI behavior tests session
192
+ - `sarah-viewer` - Viewer role session
193
+
194
+ ## Related Files
195
+
196
+ - `core/components/teams/TeamSwitcherCompact.tsx` - Main component
197
+ - `core/components/teams/TeamSwitchModal.tsx` - Transition modal
198
+ - `test/cypress/src/classes/components/teams/TeamSwitcher.js` - POM class