@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,439 @@
1
+ /**
2
+ * Customers Entity POM
3
+ *
4
+ * Entity-specific Page Object Model for Customers in Default theme.
5
+ * Extends generic EntityList/EntityForm with customer-specific methods.
6
+ * Includes API-aware methods for deterministic testing with cy.intercept().
7
+ *
8
+ * Convention: customers-{component}-{detail}
9
+ * Examples: customers-form, customers-field-name, customers-row-{id}
10
+ */
11
+
12
+ import entitiesConfig from '../../fixtures/entities.json'
13
+ import { ApiInterceptor } from '../helpers/ApiInterceptor'
14
+
15
+ const customersEntity = entitiesConfig.entities.customers
16
+ const slug = customersEntity.slug
17
+
18
+ export interface CustomerFormData {
19
+ name: string
20
+ account: string
21
+ office: string
22
+ phone?: string
23
+ salesRep?: string
24
+ visitDays?: string[]
25
+ contactDays?: string[]
26
+ }
27
+
28
+ export class CustomersPOM {
29
+ // ============================================
30
+ // STATIC CONFIG
31
+ // ============================================
32
+
33
+ static get entityConfig() {
34
+ return customersEntity
35
+ }
36
+
37
+ static get slug() {
38
+ return slug
39
+ }
40
+
41
+ // ============================================
42
+ // API INTERCEPTOR (for deterministic waits)
43
+ // ============================================
44
+
45
+ private static _api: ApiInterceptor | null = null
46
+
47
+ /**
48
+ * Get the API interceptor instance for customers
49
+ * Lazy-initialized on first access
50
+ */
51
+ static get api(): ApiInterceptor {
52
+ if (!this._api) {
53
+ this._api = new ApiInterceptor(slug)
54
+ }
55
+ return this._api
56
+ }
57
+
58
+ /**
59
+ * Setup API intercepts for CRUD operations
60
+ * Call this in beforeEach BEFORE navigation
61
+ */
62
+ static setupApiIntercepts(): typeof CustomersPOM {
63
+ this.api.setupCrudIntercepts()
64
+ return this
65
+ }
66
+
67
+ // ============================================
68
+ // SELECTORS
69
+ // ============================================
70
+
71
+ static get selectors() {
72
+ return {
73
+ // List page (matches EntityList.tsx createCyId patterns)
74
+ page: `[data-cy="${slug}-list"]`,
75
+ table: `[data-cy="${slug}-table"]`,
76
+ createBtn: `[data-cy="${slug}-add"]`,
77
+ searchInput: `[data-cy="${slug}-search"]`,
78
+ rowGeneric: `[data-cy^="${slug}-row-"]`,
79
+ row: (id: string) => `[data-cy="${slug}-row-${id}"]`,
80
+
81
+ // Form page (EntityForm patterns)
82
+ formPage: `[data-cy="${slug}-form-page"]`,
83
+ form: `[data-cy="${slug}-form"]`,
84
+ formSubmit: `[data-cy="${slug}-form-submit"]`,
85
+ formCancel: `[data-cy="${slug}-form-cancel"]`,
86
+
87
+ // Fields (EntityForm field patterns)
88
+ field: (name: string) => `[data-cy="${slug}-field-${name}"]`,
89
+ fieldInput: (name: string) => `[data-cy="${slug}-field-${name}"] input`,
90
+ fieldTextarea: (name: string) => `[data-cy="${slug}-field-${name}"] textarea`,
91
+ fieldSelect: (name: string) => `[data-cy="${slug}-field-${name}"] [role="combobox"]`,
92
+ fieldOption: (name: string, value: string) => `[data-cy="${slug}-field-${name}-option-${value}"]`,
93
+
94
+ // Detail page actions (EntityDetail patterns)
95
+ detailEdit: `[data-cy="${slug}-edit"]`,
96
+ detailDelete: `[data-cy="${slug}-delete"]`,
97
+
98
+ // Row menu actions (EntityTable patterns)
99
+ menuTrigger: (id: string) => `[data-cy="${slug}-menu-${id}"]`,
100
+ menuEdit: (id: string) => `[data-cy="${slug}-menu-edit-${id}"]`,
101
+ menuDelete: (id: string) => `[data-cy="${slug}-menu-delete-${id}"]`,
102
+ menuView: (id: string) => `[data-cy="${slug}-menu-view-${id}"]`,
103
+
104
+ // Dialogs (EntityDetailWrapper patterns)
105
+ confirmDelete: `[data-cy="confirm-delete"]`,
106
+ confirmDeleteBtn: `[data-cy="confirm-delete"]`,
107
+ cancelDeleteBtn: `[data-cy="cancel-delete"]`,
108
+ }
109
+ }
110
+
111
+ // ============================================
112
+ // NAVIGATION
113
+ // ============================================
114
+
115
+ static visitList() {
116
+ cy.visit(`/dashboard/${slug}`)
117
+ return this
118
+ }
119
+
120
+ static visitCreate() {
121
+ cy.visit(`/dashboard/${slug}/create`)
122
+ return this
123
+ }
124
+
125
+ static visitEdit(id: string) {
126
+ cy.visit(`/dashboard/${slug}/${id}/edit`)
127
+ return this
128
+ }
129
+
130
+ static visitDetail(id: string) {
131
+ cy.visit(`/dashboard/${slug}/${id}`)
132
+ return this
133
+ }
134
+
135
+ // ============================================
136
+ // API-AWARE NAVIGATION
137
+ // ============================================
138
+
139
+ /**
140
+ * Visit list page with API intercepts and wait for data load
141
+ * Replaces: visitList() + cy.wait(2000)
142
+ */
143
+ static visitListWithApiWait(): typeof CustomersPOM {
144
+ this.setupApiIntercepts()
145
+ this.visitList()
146
+ this.api.waitForList()
147
+ return this
148
+ }
149
+
150
+ /**
151
+ * Visit edit page with API wait for data load
152
+ */
153
+ static visitEditWithApiWait(id: string): typeof CustomersPOM {
154
+ this.setupApiIntercepts()
155
+ this.visitEdit(id)
156
+ this.waitForFormLoad()
157
+ return this
158
+ }
159
+
160
+ /**
161
+ * Visit detail page with API wait for data load
162
+ */
163
+ static visitDetailWithApiWait(id: string): typeof CustomersPOM {
164
+ this.setupApiIntercepts()
165
+ this.visitDetail(id)
166
+ this.waitForDetailLoad()
167
+ return this
168
+ }
169
+
170
+ // ============================================
171
+ // API-AWARE CRUD WORKFLOWS
172
+ // ============================================
173
+
174
+ /**
175
+ * Create customer with deterministic API waits
176
+ * Replaces: fillForm() + submit() + cy.wait(2000)
177
+ */
178
+ static createWithApiWait(data: CustomerFormData): typeof CustomersPOM {
179
+ this.clickCreate()
180
+ this.waitForFormLoad()
181
+ this.fillCustomerForm(data)
182
+ this.submitForm()
183
+ this.api.waitForCreate()
184
+ this.api.waitForRefresh()
185
+ return this
186
+ }
187
+
188
+ /**
189
+ * Update customer with deterministic API waits
190
+ * Note: Does NOT include waitForRefresh() as behavior varies by UI
191
+ * (may stay on form or redirect to list). Add .api.waitForRefresh() if needed.
192
+ */
193
+ static updateWithApiWait(data: Partial<CustomerFormData>): typeof CustomersPOM {
194
+ if (data.name) this.fillTextField('name', data.name)
195
+ if (data.account) this.fillTextField('account', data.account)
196
+ if (data.office) this.fillTextField('office', data.office)
197
+ if (data.phone) this.fillTextField('phone', data.phone)
198
+ if (data.salesRep) this.fillTextField('salesRep', data.salesRep)
199
+ this.submitForm()
200
+ this.api.waitForUpdate()
201
+ return this
202
+ }
203
+
204
+ /**
205
+ * Delete customer with deterministic API waits
206
+ * Flow: Navigate to detail page -> Click delete -> Confirm -> Wait for delete
207
+ */
208
+ static deleteWithApiWait(id: string): typeof CustomersPOM {
209
+ this.visitDetailWithApiWait(id)
210
+ this.clickDetailDelete()
211
+ this.confirmDelete()
212
+ this.api.waitForDelete()
213
+ return this
214
+ }
215
+
216
+ /**
217
+ * Delete customer from list by name (with API waits)
218
+ * Finds customer, navigates to detail, deletes
219
+ */
220
+ static deleteByNameWithApiWait(name: string): typeof CustomersPOM {
221
+ this.clickCustomerInList(name)
222
+ this.waitForDetailLoad()
223
+ this.clickDetailDelete()
224
+ this.confirmDelete()
225
+ this.api.waitForDelete()
226
+ return this
227
+ }
228
+
229
+ // ============================================
230
+ // WAIT METHODS
231
+ // ============================================
232
+
233
+ static waitForListLoad() {
234
+ cy.url().should('include', `/dashboard/${slug}`)
235
+ cy.get(this.selectors.page, { timeout: 15000 }).should('be.visible')
236
+ return this
237
+ }
238
+
239
+ static waitForFormLoad() {
240
+ cy.get(this.selectors.form, { timeout: 15000 }).should('be.visible')
241
+ return this
242
+ }
243
+
244
+ static waitForDetailLoad() {
245
+ cy.url().should('match', new RegExp(`/dashboard/${slug}/[a-z0-9-]+$`))
246
+ cy.get(this.selectors.detailEdit, { timeout: 15000 }).should('be.visible')
247
+ return this
248
+ }
249
+
250
+ // ============================================
251
+ // LIST INTERACTIONS
252
+ // ============================================
253
+
254
+ static clickCreate() {
255
+ cy.get(this.selectors.createBtn).click()
256
+ return this
257
+ }
258
+
259
+ static search(term: string) {
260
+ cy.get(this.selectors.searchInput).clear().type(term)
261
+ return this
262
+ }
263
+
264
+ static clearSearch() {
265
+ cy.get(this.selectors.searchInput).clear()
266
+ return this
267
+ }
268
+
269
+ // ============================================
270
+ // FORM METHODS
271
+ // ============================================
272
+
273
+ static fillTextField(fieldName: string, value: string) {
274
+ cy.get(this.selectors.fieldInput(fieldName)).clear().type(value)
275
+ return this
276
+ }
277
+
278
+ static fillTextarea(fieldName: string, value: string) {
279
+ cy.get(this.selectors.fieldTextarea(fieldName)).clear().type(value)
280
+ return this
281
+ }
282
+
283
+ static selectOption(fieldName: string, optionValue: string) {
284
+ cy.get(this.selectors.fieldSelect(fieldName)).click()
285
+ cy.get(this.selectors.fieldOption(fieldName, optionValue)).click()
286
+ return this
287
+ }
288
+
289
+ static submitForm() {
290
+ cy.get(this.selectors.formSubmit).click()
291
+ return this
292
+ }
293
+
294
+ static cancelForm() {
295
+ cy.get(this.selectors.formCancel).click()
296
+ return this
297
+ }
298
+
299
+ // ============================================
300
+ // DETAIL PAGE INTERACTIONS
301
+ // ============================================
302
+
303
+ /**
304
+ * Click edit button on detail page
305
+ * (EntityDetail pattern - edit/delete are on detail page, not list)
306
+ * Uses .first() because there may be multiple edit buttons (header + quick actions)
307
+ */
308
+ static clickDetailEdit() {
309
+ cy.get(this.selectors.detailEdit).first().click()
310
+ return this
311
+ }
312
+
313
+ /**
314
+ * Click delete button on detail page
315
+ * Uses .first() because there may be multiple delete buttons
316
+ */
317
+ static clickDetailDelete() {
318
+ cy.get(this.selectors.detailDelete).first().click()
319
+ return this
320
+ }
321
+
322
+ /**
323
+ * Click customer name in list to navigate to detail page
324
+ */
325
+ static clickCustomerInList(name: string) {
326
+ cy.contains(this.selectors.rowGeneric, name).within(() => {
327
+ cy.get('a').first().click()
328
+ })
329
+ return this
330
+ }
331
+
332
+ /**
333
+ * Fill customer form with provided data
334
+ */
335
+ static fillCustomerForm(data: CustomerFormData) {
336
+ if (data.name) {
337
+ this.fillTextField('name', data.name)
338
+ }
339
+ if (data.account) {
340
+ this.fillTextField('account', data.account)
341
+ }
342
+ if (data.office) {
343
+ this.fillTextField('office', data.office)
344
+ }
345
+ if (data.phone) {
346
+ this.fillTextField('phone', data.phone)
347
+ }
348
+ if (data.salesRep) {
349
+ this.fillTextField('salesRep', data.salesRep)
350
+ }
351
+ return this
352
+ }
353
+
354
+ // ============================================
355
+ // ACTIONS
356
+ // ============================================
357
+
358
+ static openRowMenu(id: string) {
359
+ cy.get(this.selectors.menuTrigger(id)).click()
360
+ return this
361
+ }
362
+
363
+ static clickMenuEdit(id: string) {
364
+ cy.get(this.selectors.menuEdit(id)).click()
365
+ return this
366
+ }
367
+
368
+ static clickMenuDelete(id: string) {
369
+ cy.get(this.selectors.menuDelete(id)).click()
370
+ return this
371
+ }
372
+
373
+ static clickMenuView(id: string) {
374
+ cy.get(this.selectors.menuView(id)).click()
375
+ return this
376
+ }
377
+
378
+ static confirmDelete() {
379
+ cy.get(this.selectors.confirmDeleteBtn).click()
380
+ return this
381
+ }
382
+
383
+ static cancelDelete() {
384
+ cy.get(this.selectors.cancelDeleteBtn).click()
385
+ return this
386
+ }
387
+
388
+ /**
389
+ * Delete a customer by finding it in the list and clicking delete
390
+ */
391
+ static deleteCustomerByText(text: string) {
392
+ cy.contains(this.selectors.rowGeneric, text).within(() => {
393
+ cy.get('[data-cy*="delete"], button[aria-label*="delete" i]').click()
394
+ })
395
+ this.confirmDelete()
396
+ return this
397
+ }
398
+
399
+ /**
400
+ * Edit a customer by finding it in the list and clicking edit
401
+ */
402
+ static editCustomerByText(text: string) {
403
+ cy.contains(this.selectors.rowGeneric, text).within(() => {
404
+ cy.get('[data-cy*="edit"], button[aria-label*="edit" i]').click()
405
+ })
406
+ return this
407
+ }
408
+
409
+ // ============================================
410
+ // ASSERTIONS
411
+ // ============================================
412
+
413
+ static assertCustomerInList(name: string) {
414
+ cy.contains(name).should('be.visible')
415
+ return this
416
+ }
417
+
418
+ static assertCustomerNotInList(name: string) {
419
+ cy.contains(name).should('not.exist')
420
+ return this
421
+ }
422
+
423
+ static assertPageVisible() {
424
+ cy.get(this.selectors.page).should('be.visible')
425
+ return this
426
+ }
427
+
428
+ static assertFormVisible() {
429
+ cy.get(this.selectors.form).should('be.visible')
430
+ return this
431
+ }
432
+
433
+ static assertTableVisible() {
434
+ cy.get(this.selectors.table).should('be.visible')
435
+ return this
436
+ }
437
+ }
438
+
439
+ export default CustomersPOM
@@ -0,0 +1,160 @@
1
+ /**
2
+ * DevKeyringPOM - Page Object Model for Development Keyring
3
+ *
4
+ * POM for the development keyring (test credential selector).
5
+ * This component allows quick switching between test users in development mode.
6
+ *
7
+ * NOTE: DevKeyring only FILLS the login form with credentials.
8
+ * You must still submit the form to complete login.
9
+ *
10
+ * IMPORTANT: Always use email-based methods (selectUserByEmail, quickLoginByEmail)
11
+ * to make tests resilient to user order changes in the DevKeyring config.
12
+ *
13
+ * @version 3.0 - Uses centralized selectors from cySelector()
14
+ */
15
+ import { BasePOM } from '../core/BasePOM'
16
+ import { cySelector } from '../selectors'
17
+
18
+ export class DevKeyringPOM extends BasePOM {
19
+ /**
20
+ * Selectors using centralized cySelector()
21
+ */
22
+ get selectors() {
23
+ return {
24
+ container: cySelector('auth.devKeyring.container'),
25
+ trigger: cySelector('auth.devKeyring.trigger'),
26
+ content: cySelector('auth.devKeyring.content'),
27
+ // Prefix selector for all user items (cySelector doesn't support prefix matching)
28
+ userItem: '[data-cy^="devkeyring-user-"]',
29
+ userByIndex: (index: number) => cySelector('auth.devKeyring.user', { index }),
30
+ // Login form selectors (used after filling credentials)
31
+ loginSubmit: cySelector('auth.login.submit'),
32
+ loginEmail: '#email',
33
+ loginPassword: '#password',
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Factory method - creates a new instance
39
+ */
40
+ static create(): DevKeyringPOM {
41
+ return new DevKeyringPOM()
42
+ }
43
+
44
+ // ============================================
45
+ // Validation Methods
46
+ // ============================================
47
+
48
+ /**
49
+ * Validate keyring container is visible
50
+ */
51
+ validateVisible(): this {
52
+ cy.get(this.selectors.container).should('be.visible')
53
+ return this
54
+ }
55
+
56
+ /**
57
+ * Validate keyring is not visible (production mode)
58
+ */
59
+ validateNotVisible(): this {
60
+ cy.get(this.selectors.container).should('not.exist')
61
+ return this
62
+ }
63
+
64
+ // ============================================
65
+ // Dropdown Methods
66
+ // ============================================
67
+
68
+ /**
69
+ * Open the keyring dropdown
70
+ */
71
+ open(): this {
72
+ cy.get(this.selectors.trigger).click()
73
+ cy.get(this.selectors.content).should('be.visible')
74
+ return this
75
+ }
76
+
77
+ /**
78
+ * Close the keyring dropdown
79
+ */
80
+ close(): this {
81
+ cy.get('body').click(0, 0)
82
+ cy.get(this.selectors.content).should('not.be.visible')
83
+ return this
84
+ }
85
+
86
+ // ============================================
87
+ // User Selection Methods
88
+ // ============================================
89
+
90
+ /**
91
+ * Select a user by email (fills the form, does NOT submit)
92
+ */
93
+ selectUserByEmail(email: string): this {
94
+ this.open()
95
+ cy.get(this.selectors.userItem).contains(email).click()
96
+ // Wait for form to be filled
97
+ cy.get(this.selectors.loginEmail).should('have.value', email)
98
+ return this
99
+ }
100
+
101
+ /**
102
+ * Submit the login form after credentials are filled
103
+ */
104
+ submitLogin(): this {
105
+ cy.get(this.selectors.loginSubmit).click()
106
+ return this
107
+ }
108
+
109
+ /**
110
+ * Quick login with a specific user by email (fills form AND submits)
111
+ * This is the preferred method for login as it's resilient to user order changes.
112
+ */
113
+ quickLoginByEmail(email: string): this {
114
+ // 1. Select user by email (opens dropdown + fills form)
115
+ this.selectUserByEmail(email)
116
+
117
+ // 2. Submit the login form
118
+ this.submitLogin()
119
+
120
+ // 3. Wait for login to complete
121
+ cy.url().should('include', '/dashboard', { timeout: 10000 })
122
+
123
+ return this
124
+ }
125
+
126
+ // ============================================
127
+ // User Validation Methods
128
+ // ============================================
129
+
130
+ /**
131
+ * Validate the number of available users
132
+ */
133
+ validateUserCount(count: number): this {
134
+ this.open()
135
+ cy.get(this.selectors.userItem).should('have.length', count)
136
+ return this
137
+ }
138
+
139
+ /**
140
+ * Validate a user exists in the keyring
141
+ */
142
+ validateUserExists(email: string): this {
143
+ this.open()
144
+ cy.get(this.selectors.userItem).contains(email).should('exist')
145
+ return this
146
+ }
147
+
148
+ /**
149
+ * Get all user emails from the keyring
150
+ */
151
+ getUserEmails(): Cypress.Chainable<string[]> {
152
+ this.open()
153
+ return cy.get(this.selectors.userItem)
154
+ .then($elements => {
155
+ return Cypress._.map($elements, el => el.innerText.trim())
156
+ })
157
+ }
158
+ }
159
+
160
+ export default DevKeyringPOM