@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,370 @@
1
+ /**
2
+ * Post Editor POM
3
+ *
4
+ * Page Object Model for the Post Editor (create/edit posts with blocks).
5
+ * Reuses Page Builder patterns for block management.
6
+ *
7
+ * Convention: post-{component}-{element}
8
+ * Based on selectors documented in tests.md
9
+ */
10
+
11
+ import { ApiInterceptor } from '../helpers/ApiInterceptor'
12
+
13
+ export interface PostFormData {
14
+ title: string
15
+ slug: string
16
+ excerpt?: string
17
+ featuredImage?: string
18
+ categoryIds?: string[]
19
+ published?: boolean
20
+ locale?: 'en' | 'es'
21
+ }
22
+
23
+ export interface BlockData {
24
+ slug: string
25
+ props?: Record<string, unknown>
26
+ }
27
+
28
+ export class PostEditorPOM {
29
+ // ============================================
30
+ // STATIC CONFIG
31
+ // ============================================
32
+
33
+ static get slug() {
34
+ return 'posts'
35
+ }
36
+
37
+ // ============================================
38
+ // API INTERCEPTOR (for deterministic waits)
39
+ // ============================================
40
+
41
+ private static _api: ApiInterceptor | null = null
42
+
43
+ /**
44
+ * Get the API interceptor instance for posts
45
+ * Lazy-initialized on first access
46
+ */
47
+ static get api(): ApiInterceptor {
48
+ if (!this._api) {
49
+ this._api = new ApiInterceptor(this.slug)
50
+ }
51
+ return this._api
52
+ }
53
+
54
+ /**
55
+ * Setup API intercepts for CRUD operations
56
+ * Call this in beforeEach BEFORE navigation
57
+ */
58
+ static setupApiIntercepts(): typeof PostEditorPOM {
59
+ this.api.setupCrudIntercepts()
60
+ return this
61
+ }
62
+
63
+ // ============================================
64
+ // SELECTORS - Post Editor
65
+ // ============================================
66
+
67
+ static get editorSelectors() {
68
+ return {
69
+ // Editor container
70
+ editor: '[data-cy="post-editor"]',
71
+
72
+ // Post form fields
73
+ titleInput: '[data-cy="post-title-input"]',
74
+ slugInput: '[data-cy="post-slug-input"]',
75
+ excerptInput: '[data-cy="post-excerpt-input"]',
76
+ featuredImage: '[data-cy="post-featured-image"]',
77
+ categoriesSelect: '[data-cy="post-categories-select"]',
78
+ publishToggle: '[data-cy="post-publish-toggle"]',
79
+ saveButton: '[data-cy="post-save-button"]',
80
+ publishButton: '[data-cy="post-publish-button"]',
81
+
82
+ // Block editor (reuses page builder components)
83
+ blockSelector: '[data-cy="post-block-selector"]',
84
+ blockEditor: '[data-cy="post-block-editor"]',
85
+
86
+ // From PageBuilderPOM patterns
87
+ blockPicker: '[data-cy="block-picker"]',
88
+ blockSearchInput: '[data-cy="block-search-input"]',
89
+ blockItem: (slug: string) => `[data-cy="block-item-${slug}"]`,
90
+ addBlockBtn: (slug: string) => `[data-cy="add-block-${slug}"]`,
91
+
92
+ blockCanvas: '[data-cy="block-canvas"]',
93
+ sortableBlock: (id: string) => `[data-cy="sortable-block-${id}"]`,
94
+ removeBlock: (id: string) => `[data-cy="remove-block-${id}"]`,
95
+ sortableBlockGeneric: '[data-cy^="sortable-block-"]',
96
+
97
+ // Settings panel
98
+ settingsPanel: '[data-cy="block-settings-panel"]',
99
+ fieldInput: (name: string) => `[data-cy="field-${name}"]`,
100
+ }
101
+ }
102
+
103
+ // ============================================
104
+ // NAVIGATION
105
+ // ============================================
106
+
107
+ static visitCreate() {
108
+ cy.visit('/dashboard/posts/create')
109
+ return this
110
+ }
111
+
112
+ static visitEdit(id: string) {
113
+ cy.visit(`/dashboard/posts/${id}/edit`)
114
+ return this
115
+ }
116
+
117
+ // ============================================
118
+ // API-AWARE NAVIGATION
119
+ // ============================================
120
+
121
+ /**
122
+ * Visit create page with API intercepts
123
+ */
124
+ static visitCreateWithApiWait(): typeof PostEditorPOM {
125
+ this.setupApiIntercepts()
126
+ this.visitCreate()
127
+ return this
128
+ }
129
+
130
+ /**
131
+ * Visit edit page with API intercepts
132
+ */
133
+ static visitEditWithApiWait(id: string): typeof PostEditorPOM {
134
+ this.setupApiIntercepts()
135
+ this.visitEdit(id)
136
+ return this
137
+ }
138
+
139
+ // ============================================
140
+ // WAIT METHODS
141
+ // ============================================
142
+
143
+ static waitForEditorLoad() {
144
+ cy.get(this.editorSelectors.editor, { timeout: 15000 }).should('be.visible')
145
+ return this
146
+ }
147
+
148
+ static waitForBlockPickerLoad() {
149
+ cy.get(this.editorSelectors.blockPicker, { timeout: 10000 }).should('be.visible')
150
+ return this
151
+ }
152
+
153
+ // ============================================
154
+ // POST FORM INTERACTIONS
155
+ // ============================================
156
+
157
+ static setTitle(title: string) {
158
+ cy.get(this.editorSelectors.titleInput).clear().type(title)
159
+ return this
160
+ }
161
+
162
+ static setSlug(slug: string) {
163
+ cy.get(this.editorSelectors.slugInput).clear().type(slug)
164
+ return this
165
+ }
166
+
167
+ static setExcerpt(excerpt: string) {
168
+ cy.get(this.editorSelectors.excerptInput).clear().type(excerpt)
169
+ return this
170
+ }
171
+
172
+ static setFeaturedImage(url: string) {
173
+ cy.get(this.editorSelectors.featuredImage).clear().type(url)
174
+ return this
175
+ }
176
+
177
+ static selectCategories(categoryNames: string[]) {
178
+ // Click on categories select to open
179
+ cy.get(this.editorSelectors.categoriesSelect).click()
180
+
181
+ // Select each category
182
+ categoryNames.forEach((name) => {
183
+ cy.contains('[role="option"]', name).click()
184
+ })
185
+
186
+ // Click outside to close
187
+ cy.get(this.editorSelectors.editor).click()
188
+ return this
189
+ }
190
+
191
+ static togglePublish() {
192
+ cy.get(this.editorSelectors.publishToggle).click()
193
+ return this
194
+ }
195
+
196
+ static savePost() {
197
+ cy.get(this.editorSelectors.saveButton).click()
198
+ return this
199
+ }
200
+
201
+ static publishPost() {
202
+ cy.get(this.editorSelectors.publishButton).click()
203
+ return this
204
+ }
205
+
206
+ // ============================================
207
+ // API-AWARE SAVE OPERATIONS
208
+ // ============================================
209
+
210
+ /**
211
+ * Save post and wait for API response
212
+ */
213
+ static savePostWithApiWait(): typeof PostEditorPOM {
214
+ this.savePost()
215
+ this.api.waitForCreate()
216
+ return this
217
+ }
218
+
219
+ /**
220
+ * Update post and wait for API response
221
+ */
222
+ static updatePostWithApiWait(): typeof PostEditorPOM {
223
+ this.savePost()
224
+ this.api.waitForUpdate()
225
+ return this
226
+ }
227
+
228
+ // ============================================
229
+ // BLOCK EDITOR INTERACTIONS
230
+ // ============================================
231
+
232
+ static searchBlocks(term: string) {
233
+ cy.get(this.editorSelectors.blockSearchInput).clear().type(term)
234
+ return this
235
+ }
236
+
237
+ static addBlock(slug: string) {
238
+ cy.get(this.editorSelectors.addBlockBtn(slug)).click()
239
+ return this
240
+ }
241
+
242
+ static selectBlock(blockId: string) {
243
+ cy.get(this.editorSelectors.sortableBlock(blockId)).click()
244
+ return this
245
+ }
246
+
247
+ static removeBlock(blockId: string) {
248
+ cy.get(this.editorSelectors.removeBlock(blockId)).click()
249
+ return this
250
+ }
251
+
252
+ static fillBlockField(fieldName: string, value: string) {
253
+ cy.get(this.editorSelectors.fieldInput(fieldName)).find('input, textarea').first().clear().type(value)
254
+ return this
255
+ }
256
+
257
+ // ============================================
258
+ // COMPLETE WORKFLOWS
259
+ // ============================================
260
+
261
+ /**
262
+ * Create a new post with specified data and blocks
263
+ */
264
+ static createPost(data: PostFormData, blocks: BlockData[] = []) {
265
+ this.visitCreate()
266
+ this.waitForEditorLoad()
267
+
268
+ // Set post fields
269
+ this.setTitle(data.title)
270
+ this.setSlug(data.slug)
271
+
272
+ if (data.excerpt) {
273
+ this.setExcerpt(data.excerpt)
274
+ }
275
+
276
+ if (data.featuredImage) {
277
+ this.setFeaturedImage(data.featuredImage)
278
+ }
279
+
280
+ if (data.categoryIds && data.categoryIds.length > 0) {
281
+ this.selectCategories(data.categoryIds)
282
+ }
283
+
284
+ // Add blocks
285
+ blocks.forEach((block) => {
286
+ this.addBlock(block.slug)
287
+ })
288
+
289
+ // Publish or save
290
+ if (data.published) {
291
+ this.publishPost()
292
+ } else {
293
+ this.savePost()
294
+ }
295
+
296
+ return this
297
+ }
298
+
299
+ /**
300
+ * Create post with API wait for deterministic testing
301
+ */
302
+ static createPostWithApiWait(data: PostFormData, blocks: BlockData[] = []): typeof PostEditorPOM {
303
+ this.setupApiIntercepts()
304
+ this.visitCreate()
305
+ this.waitForEditorLoad()
306
+
307
+ this.setTitle(data.title)
308
+ this.setSlug(data.slug)
309
+
310
+ if (data.excerpt) this.setExcerpt(data.excerpt)
311
+ if (data.featuredImage) this.setFeaturedImage(data.featuredImage)
312
+
313
+ blocks.forEach((block) => this.addBlock(block.slug))
314
+
315
+ this.savePost()
316
+ this.api.waitForCreate()
317
+ return this
318
+ }
319
+
320
+ // ============================================
321
+ // ASSERTIONS
322
+ // ============================================
323
+
324
+ static assertEditorVisible() {
325
+ cy.get(this.editorSelectors.editor).should('be.visible')
326
+ return this
327
+ }
328
+
329
+ static assertBlockPickerVisible() {
330
+ cy.get(this.editorSelectors.blockPicker).should('be.visible')
331
+ return this
332
+ }
333
+
334
+ static assertBlockInPicker(blockSlug: string) {
335
+ cy.get(this.editorSelectors.blockItem(blockSlug)).should('be.visible')
336
+ return this
337
+ }
338
+
339
+ static assertBlockNotInPicker(blockSlug: string) {
340
+ cy.get(this.editorSelectors.blockItem(blockSlug)).should('not.exist')
341
+ return this
342
+ }
343
+
344
+ static assertBlockCount(count: number) {
345
+ cy.get(this.editorSelectors.sortableBlockGeneric).should('have.length', count)
346
+ return this
347
+ }
348
+
349
+ static assertTitleValue(title: string) {
350
+ cy.get(this.editorSelectors.titleInput).should('have.value', title)
351
+ return this
352
+ }
353
+
354
+ static assertSlugValue(slug: string) {
355
+ cy.get(this.editorSelectors.slugInput).should('have.value', slug)
356
+ return this
357
+ }
358
+
359
+ static assertSaveSuccess() {
360
+ cy.contains('saved', { matchCase: false }).should('be.visible')
361
+ return this
362
+ }
363
+
364
+ static assertPublishSuccess() {
365
+ cy.contains('published', { matchCase: false }).should('be.visible')
366
+ return this
367
+ }
368
+ }
369
+
370
+ export default PostEditorPOM
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Posts List POM
3
+ *
4
+ * Page Object Model for the Posts list page in the admin dashboard.
5
+ * Covers list viewing, search, filtering, and navigation to create/edit posts.
6
+ *
7
+ * Convention: posts-{component}-{element}
8
+ * Based on selectors documented in tests.md
9
+ */
10
+
11
+ import { ApiInterceptor } from '../helpers/ApiInterceptor'
12
+
13
+ export interface PostFormData {
14
+ title: string
15
+ slug: string
16
+ excerpt?: string
17
+ featuredImage?: string
18
+ categoryIds?: string[]
19
+ published?: boolean
20
+ locale?: 'en' | 'es'
21
+ }
22
+
23
+ export class PostsListPOM {
24
+ // ============================================
25
+ // STATIC CONFIG
26
+ // ============================================
27
+
28
+ static get slug() {
29
+ return 'posts'
30
+ }
31
+
32
+ // ============================================
33
+ // API INTERCEPTOR (for deterministic waits)
34
+ // ============================================
35
+
36
+ private static _api: ApiInterceptor | null = null
37
+
38
+ /**
39
+ * Get the API interceptor instance for posts
40
+ * Lazy-initialized on first access
41
+ */
42
+ static get api(): ApiInterceptor {
43
+ if (!this._api) {
44
+ this._api = new ApiInterceptor(this.slug)
45
+ }
46
+ return this._api
47
+ }
48
+
49
+ /**
50
+ * Setup API intercepts for CRUD operations
51
+ * Call this in beforeEach BEFORE navigation
52
+ */
53
+ static setupApiIntercepts(): typeof PostsListPOM {
54
+ this.api.setupCrudIntercepts()
55
+ return this
56
+ }
57
+
58
+ // ============================================
59
+ // SELECTORS - Posts List
60
+ // ============================================
61
+
62
+ static get listSelectors() {
63
+ // Using entity testing convention: {slug}-{component}
64
+ // Based on createCyId(entityConfig.slug, component) from testing-utils.ts
65
+ return {
66
+ page: '[data-cy="posts-page"]',
67
+ title: '[data-cy="posts-title"]',
68
+ table: '[data-cy="posts-table"], table',
69
+ createBtn: '[data-cy="posts-add"]',
70
+ searchContainer: '[data-cy="posts-search"]',
71
+ searchInput: '[data-cy="posts-search-input"]',
72
+ categoryFilter: '[data-cy="posts-filter-categoryId"]',
73
+ statusFilter: '[data-cy="posts-filter-status"]',
74
+ pagination: '[data-cy="posts-pagination"]',
75
+ row: (id: string) => `[data-cy="posts-row-${id}"]`,
76
+ rowGeneric: 'table tbody tr',
77
+ // Row menu actions (EntityTable patterns)
78
+ menuTrigger: (id: string) => `[data-cy="posts-menu-${id}"]`,
79
+ menuEdit: (id: string) => `[data-cy="posts-menu-edit-${id}"]`,
80
+ menuDelete: (id: string) => `[data-cy="posts-menu-delete-${id}"]`,
81
+ menuView: (id: string) => `[data-cy="posts-menu-view-${id}"]`,
82
+ confirmDelete: '[data-cy="posts-confirm-delete"], [role="dialog"]',
83
+ confirmDeleteBtn: '[data-cy="posts-confirm-delete-btn"], [role="dialog"] button:contains("Delete"), button.bg-destructive',
84
+ cancelDeleteBtn: '[data-cy="posts-cancel-delete-btn"], [role="dialog"] button:contains("Cancel"),[role="dialog"] button[type="button"]:not(.bg-destructive)',
85
+ emptyState: 'td:contains("No posts")',
86
+ }
87
+ }
88
+
89
+ // ============================================
90
+ // NAVIGATION
91
+ // ============================================
92
+
93
+ static visitList() {
94
+ cy.visit('/dashboard/posts')
95
+ return this
96
+ }
97
+
98
+ static visitCreate() {
99
+ cy.visit('/dashboard/posts/create')
100
+ return this
101
+ }
102
+
103
+ static visitEdit(id: string) {
104
+ cy.visit(`/dashboard/posts/${id}/edit`)
105
+ return this
106
+ }
107
+
108
+ // ============================================
109
+ // API-AWARE NAVIGATION
110
+ // ============================================
111
+
112
+ /**
113
+ * Visit list page with API intercepts and wait for data load
114
+ */
115
+ static visitListWithApiWait(): typeof PostsListPOM {
116
+ this.setupApiIntercepts()
117
+ this.visitList()
118
+ this.api.waitForList()
119
+ return this
120
+ }
121
+
122
+ // ============================================
123
+ // WAIT METHODS
124
+ // ============================================
125
+
126
+ static waitForListLoad() {
127
+ cy.url().should('include', '/dashboard/posts')
128
+ cy.get(this.listSelectors.page, { timeout: 15000 }).should('exist')
129
+ return this
130
+ }
131
+
132
+ // ============================================
133
+ // LIST PAGE INTERACTIONS
134
+ // ============================================
135
+
136
+ static clickCreatePost() {
137
+ cy.get(this.listSelectors.createBtn).click()
138
+ return this
139
+ }
140
+
141
+ static searchPosts(term: string) {
142
+ cy.get(this.listSelectors.searchInput).clear().type(term)
143
+ return this
144
+ }
145
+
146
+ static clearSearch() {
147
+ cy.get(this.listSelectors.searchInput).clear()
148
+ return this
149
+ }
150
+
151
+ static filterByCategory(categoryName: string) {
152
+ cy.get(this.listSelectors.categoryFilter).click()
153
+ cy.contains('[role="option"]', categoryName).click()
154
+ return this
155
+ }
156
+
157
+ static filterByStatus(status: 'all' | 'published' | 'draft') {
158
+ cy.get(this.listSelectors.statusFilter).click()
159
+ cy.contains('[role="option"]', status).click()
160
+ return this
161
+ }
162
+
163
+ static openRowMenu(id: string) {
164
+ cy.get(this.listSelectors.menuTrigger(id)).click()
165
+ return this
166
+ }
167
+
168
+ static clickMenuEdit(id: string) {
169
+ cy.get(this.listSelectors.menuEdit(id)).click()
170
+ return this
171
+ }
172
+
173
+ static clickMenuDelete(id: string) {
174
+ cy.get(this.listSelectors.menuDelete(id)).click()
175
+ return this
176
+ }
177
+
178
+ static clickMenuView(id: string) {
179
+ cy.get(this.listSelectors.menuView(id)).click()
180
+ return this
181
+ }
182
+
183
+ static confirmDelete() {
184
+ cy.get(this.listSelectors.confirmDeleteBtn).click()
185
+ return this
186
+ }
187
+
188
+ static cancelDelete() {
189
+ cy.get(this.listSelectors.cancelDeleteBtn).click()
190
+ return this
191
+ }
192
+
193
+ // ============================================
194
+ // ASSERTIONS
195
+ // ============================================
196
+
197
+ static assertListPageVisible() {
198
+ cy.get(this.listSelectors.page).should('exist')
199
+ return this
200
+ }
201
+
202
+ static assertPostInList(title: string) {
203
+ cy.contains(this.listSelectors.rowGeneric, title).should('be.visible')
204
+ return this
205
+ }
206
+
207
+ static assertPostNotInList(title: string) {
208
+ cy.contains(this.listSelectors.rowGeneric, title).should('not.exist')
209
+ return this
210
+ }
211
+
212
+ static assertEmptyList() {
213
+ cy.get(this.listSelectors.emptyState).should('be.visible')
214
+ return this
215
+ }
216
+
217
+ static assertCategoryBadgeVisible(categoryName: string) {
218
+ cy.contains(this.listSelectors.table, categoryName).should('be.visible')
219
+ return this
220
+ }
221
+ }
222
+
223
+ export default PostsListPOM