@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,447 @@
1
+ /**
2
+ * Public Page POM
3
+ *
4
+ * Page Object Model for testing public page rendering.
5
+ * Validates that pages created with the Block Editor render correctly
6
+ * on the public-facing site.
7
+ *
8
+ * Convention: block-{slug} for rendered blocks
9
+ * Based on data-cy attributes added to block components.
10
+ */
11
+
12
+ import { cySelector } from '../selectors'
13
+
14
+ export interface PageData {
15
+ slug: string
16
+ locale?: 'en' | 'es'
17
+ }
18
+
19
+ export class PublicPagePOM {
20
+ // ============================================
21
+ // SELECTORS - Navbar (using centralized cySelector)
22
+ // ============================================
23
+
24
+ static get navbarSelectors() {
25
+ return {
26
+ container: cySelector('public.navbar.container'),
27
+ logo: cySelector('public.navbar.logo'),
28
+ loginButton: cySelector('public.navbar.loginButton'),
29
+ signupButton: cySelector('public.navbar.signupButton'),
30
+ }
31
+ }
32
+
33
+ // ============================================
34
+ // SELECTORS - Footer (using centralized cySelector)
35
+ // ============================================
36
+
37
+ static get footerSelectors() {
38
+ return {
39
+ container: cySelector('public.footer.container'),
40
+ logo: cySelector('public.footer.logo'),
41
+ }
42
+ }
43
+
44
+ // ============================================
45
+ // SELECTORS - Page Container
46
+ // ============================================
47
+
48
+ static get pageSelectors() {
49
+ return {
50
+ // Page wrapper (from PageRenderer)
51
+ page: '[data-page-id]',
52
+ pageById: (id: string) => `[data-page-id="${id}"]`,
53
+ pageBySlug: (slug: string) => `[data-page-slug="${slug}"]`,
54
+
55
+ // Generic block wrapper
56
+ blockWrapper: '[data-block-id]',
57
+ blockById: (id: string) => `[data-block-id="${id}"]`,
58
+ blockBySlug: (slug: string) => `[data-block-slug="${slug}"]`,
59
+
60
+ // Empty page state
61
+ emptyPage: '[data-cy="page-empty"]',
62
+ }
63
+ }
64
+
65
+ // ============================================
66
+ // SELECTORS - Block Components
67
+ // ============================================
68
+
69
+ static get blockSelectors() {
70
+ return {
71
+ // Core blocks (from contents/themes/default/blocks/)
72
+ hero: '[data-cy="block-hero"]',
73
+ ctaSection: '[data-cy="block-cta-section"]',
74
+ featuresGrid: '[data-cy="block-features-grid"]',
75
+ testimonials: '[data-cy="block-testimonials"]',
76
+ textContent: '[data-cy="block-text-content"]',
77
+ benefits: '[data-cy="block-benefits"]',
78
+
79
+ // Generic block selector
80
+ block: (slug: string) => `[data-cy="block-${slug}"]`,
81
+ }
82
+ }
83
+
84
+ // ============================================
85
+ // SELECTORS - Block Internal Elements
86
+ // ============================================
87
+
88
+ static get heroElements() {
89
+ return {
90
+ section: '[data-cy="block-hero"]',
91
+ title: '[data-cy="block-hero"] h1',
92
+ subtitle: '[data-cy="block-hero"] p',
93
+ ctaButton: '[data-cy="block-hero"] a[href]',
94
+ backgroundImage: '[data-cy="block-hero"] img',
95
+ }
96
+ }
97
+
98
+ static get ctaElements() {
99
+ return {
100
+ section: '[data-cy="block-cta-section"]',
101
+ title: '[data-cy="block-cta-section"] h2',
102
+ description: '[data-cy="block-cta-section"] p',
103
+ primaryButton: '[data-cy="block-cta-section"] a:first-of-type',
104
+ secondaryButton: '[data-cy="block-cta-section"] a:last-of-type',
105
+ }
106
+ }
107
+
108
+ static get featuresElements() {
109
+ return {
110
+ section: '[data-cy="block-features-grid"]',
111
+ title: '[data-cy="block-features-grid"] h2',
112
+ featureItem: '[data-cy="block-features-grid"] [class*="feature"]',
113
+ featureIcon: '[data-cy="block-features-grid"] svg',
114
+ featureTitle: '[data-cy="block-features-grid"] h3',
115
+ featureDescription: '[data-cy="block-features-grid"] p',
116
+ }
117
+ }
118
+
119
+ static get testimonialsElements() {
120
+ return {
121
+ section: '[data-cy="block-testimonials"]',
122
+ title: '[data-cy="block-testimonials"] h2',
123
+ testimonialItem: '[data-cy="block-testimonials"] [class*="testimonial"]',
124
+ quote: '[data-cy="block-testimonials"] blockquote, [data-cy="block-testimonials"] p',
125
+ authorName: '[data-cy="block-testimonials"] [class*="author"]',
126
+ authorAvatar: '[data-cy="block-testimonials"] img',
127
+ }
128
+ }
129
+
130
+ static get textContentElements() {
131
+ return {
132
+ section: '[data-cy="block-text-content"]',
133
+ content: '[data-cy="block-text-content"] [class*="prose"], [data-cy="block-text-content"] div',
134
+ }
135
+ }
136
+
137
+ static get benefitsElements() {
138
+ return {
139
+ section: '[data-cy="block-benefits"]',
140
+ title: '[data-cy="block-benefits"] h2',
141
+ benefitItem: '[data-cy="block-benefits"] [class*="benefit"]',
142
+ }
143
+ }
144
+
145
+ // ============================================
146
+ // NAVIGATION
147
+ // ============================================
148
+
149
+ static visit(slug: string, locale?: string) {
150
+ const path = locale && locale !== 'en' ? `/${locale}/${slug}` : `/${slug}`
151
+ cy.visit(path)
152
+ return this
153
+ }
154
+
155
+ static visitEnglish(slug: string) {
156
+ cy.visit(`/${slug}`)
157
+ return this
158
+ }
159
+
160
+ static visitSpanish(slug: string) {
161
+ cy.visit(`/es/${slug}`)
162
+ return this
163
+ }
164
+
165
+ // ============================================
166
+ // WAIT METHODS
167
+ // ============================================
168
+
169
+ static waitForPageLoad() {
170
+ cy.get(this.pageSelectors.page, { timeout: 15000 }).should('exist')
171
+ return this
172
+ }
173
+
174
+ static waitForBlockLoad(slug: string) {
175
+ cy.get(this.blockSelectors.block(slug), { timeout: 10000 }).should('be.visible')
176
+ return this
177
+ }
178
+
179
+ static waitForHeroLoad() {
180
+ cy.get(this.blockSelectors.hero, { timeout: 10000 }).should('be.visible')
181
+ return this
182
+ }
183
+
184
+ // ============================================
185
+ // INTERACTIONS
186
+ // ============================================
187
+
188
+ static clickHeroCta() {
189
+ cy.get(this.heroElements.ctaButton).first().click()
190
+ return this
191
+ }
192
+
193
+ static clickCtaPrimaryButton() {
194
+ cy.get(this.ctaElements.primaryButton).click()
195
+ return this
196
+ }
197
+
198
+ static clickCtaSecondaryButton() {
199
+ cy.get(this.ctaElements.secondaryButton).click()
200
+ return this
201
+ }
202
+
203
+ // ============================================
204
+ // ASSERTIONS - Page Level
205
+ // ============================================
206
+
207
+ static assertPageExists() {
208
+ cy.get(this.pageSelectors.page).should('exist')
209
+ return this
210
+ }
211
+
212
+ static assertPageHasSlug(slug: string) {
213
+ cy.get(this.pageSelectors.pageBySlug(slug)).should('exist')
214
+ return this
215
+ }
216
+
217
+ static assertPageNotFound() {
218
+ cy.contains('404').should('exist')
219
+ return this
220
+ }
221
+
222
+ static assertPageEmpty() {
223
+ cy.get(this.pageSelectors.emptyPage).should('be.visible')
224
+ return this
225
+ }
226
+
227
+ static assertBlockCount(count: number) {
228
+ cy.get(this.pageSelectors.blockWrapper).should('have.length', count)
229
+ return this
230
+ }
231
+
232
+ // ============================================
233
+ // ASSERTIONS - Block Visibility
234
+ // ============================================
235
+
236
+ static assertHeroVisible() {
237
+ cy.get(this.blockSelectors.hero).should('be.visible')
238
+ return this
239
+ }
240
+
241
+ static assertCtaVisible() {
242
+ cy.get(this.blockSelectors.ctaSection).should('be.visible')
243
+ return this
244
+ }
245
+
246
+ static assertFeaturesVisible() {
247
+ cy.get(this.blockSelectors.featuresGrid).should('be.visible')
248
+ return this
249
+ }
250
+
251
+ static assertTestimonialsVisible() {
252
+ cy.get(this.blockSelectors.testimonials).should('be.visible')
253
+ return this
254
+ }
255
+
256
+ static assertTextContentVisible() {
257
+ cy.get(this.blockSelectors.textContent).should('be.visible')
258
+ return this
259
+ }
260
+
261
+ static assertBenefitsVisible() {
262
+ cy.get(this.blockSelectors.benefits).should('be.visible')
263
+ return this
264
+ }
265
+
266
+ static assertBlockVisible(slug: string) {
267
+ cy.get(this.blockSelectors.block(slug)).should('be.visible')
268
+ return this
269
+ }
270
+
271
+ static assertBlockNotVisible(slug: string) {
272
+ cy.get(this.blockSelectors.block(slug)).should('not.exist')
273
+ return this
274
+ }
275
+
276
+ // ============================================
277
+ // ASSERTIONS - Hero Block Content
278
+ // ============================================
279
+
280
+ static assertHeroTitle(title: string) {
281
+ cy.get(this.heroElements.title).should('contain.text', title)
282
+ return this
283
+ }
284
+
285
+ static assertHeroSubtitle(subtitle: string) {
286
+ cy.get(this.heroElements.subtitle).should('contain.text', subtitle)
287
+ return this
288
+ }
289
+
290
+ static assertHeroCtaText(text: string) {
291
+ cy.get(this.heroElements.ctaButton).should('contain.text', text)
292
+ return this
293
+ }
294
+
295
+ static assertHeroCtaLink(href: string) {
296
+ cy.get(this.heroElements.ctaButton).should('have.attr', 'href', href)
297
+ return this
298
+ }
299
+
300
+ static assertHeroHasBackgroundImage() {
301
+ cy.get(this.heroElements.backgroundImage).should('exist')
302
+ return this
303
+ }
304
+
305
+ // ============================================
306
+ // ASSERTIONS - CTA Block Content
307
+ // ============================================
308
+
309
+ static assertCtaTitle(title: string) {
310
+ cy.get(this.ctaElements.title).should('contain.text', title)
311
+ return this
312
+ }
313
+
314
+ static assertCtaDescription(description: string) {
315
+ cy.get(this.ctaElements.description).should('contain.text', description)
316
+ return this
317
+ }
318
+
319
+ static assertCtaPrimaryButtonText(text: string) {
320
+ cy.get(this.ctaElements.primaryButton).should('contain.text', text)
321
+ return this
322
+ }
323
+
324
+ // ============================================
325
+ // ASSERTIONS - Features Block Content
326
+ // ============================================
327
+
328
+ static assertFeaturesTitle(title: string) {
329
+ cy.get(this.featuresElements.title).should('contain.text', title)
330
+ return this
331
+ }
332
+
333
+ static assertFeatureCount(count: number) {
334
+ cy.get(this.featuresElements.featureItem).should('have.length', count)
335
+ return this
336
+ }
337
+
338
+ static assertFeatureHasTitle(index: number, title: string) {
339
+ cy.get(this.featuresElements.featureTitle).eq(index).should('contain.text', title)
340
+ return this
341
+ }
342
+
343
+ // ============================================
344
+ // ASSERTIONS - Testimonials Block Content
345
+ // ============================================
346
+
347
+ static assertTestimonialsTitle(title: string) {
348
+ cy.get(this.testimonialsElements.title).should('contain.text', title)
349
+ return this
350
+ }
351
+
352
+ static assertTestimonialCount(count: number) {
353
+ cy.get(this.testimonialsElements.testimonialItem).should('have.length', count)
354
+ return this
355
+ }
356
+
357
+ // ============================================
358
+ // ASSERTIONS - Text Content Block
359
+ // ============================================
360
+
361
+ static assertTextContentContains(text: string) {
362
+ cy.get(this.textContentElements.content).should('contain.text', text)
363
+ return this
364
+ }
365
+
366
+ // ============================================
367
+ // ASSERTIONS - SEO & Metadata
368
+ // ============================================
369
+
370
+ static assertMetaTitle(title: string) {
371
+ cy.title().should('contain', title)
372
+ return this
373
+ }
374
+
375
+ static assertMetaDescription(description: string) {
376
+ cy.get('meta[name="description"]').should('have.attr', 'content').and('contain', description)
377
+ return this
378
+ }
379
+
380
+ static assertOgTitle(title: string) {
381
+ cy.get('meta[property="og:title"]').should('have.attr', 'content').and('contain', title)
382
+ return this
383
+ }
384
+
385
+ static assertOgDescription(description: string) {
386
+ cy.get('meta[property="og:description"]').should('have.attr', 'content').and('contain', description)
387
+ return this
388
+ }
389
+
390
+ static assertCanonicalUrl(url: string) {
391
+ cy.get('link[rel="canonical"]').should('have.attr', 'href').and('contain', url)
392
+ return this
393
+ }
394
+
395
+ // ============================================
396
+ // COMPLETE VALIDATION WORKFLOWS
397
+ // ============================================
398
+
399
+ /**
400
+ * Validate a complete page with expected blocks
401
+ */
402
+ static validatePage(slug: string, expectedBlocks: string[]) {
403
+ this.visit(slug)
404
+ this.waitForPageLoad()
405
+ this.assertPageHasSlug(slug)
406
+ this.assertBlockCount(expectedBlocks.length)
407
+
408
+ expectedBlocks.forEach((blockSlug) => {
409
+ this.assertBlockVisible(blockSlug)
410
+ })
411
+
412
+ return this
413
+ }
414
+
415
+ /**
416
+ * Validate hero block has all expected content
417
+ */
418
+ static validateHeroBlock(expectedContent: { title?: string; subtitle?: string; ctaText?: string; ctaLink?: string }) {
419
+ this.assertHeroVisible()
420
+
421
+ if (expectedContent.title) this.assertHeroTitle(expectedContent.title)
422
+ if (expectedContent.subtitle) this.assertHeroSubtitle(expectedContent.subtitle)
423
+ if (expectedContent.ctaText) this.assertHeroCtaText(expectedContent.ctaText)
424
+ if (expectedContent.ctaLink) this.assertHeroCtaLink(expectedContent.ctaLink)
425
+
426
+ return this
427
+ }
428
+
429
+ /**
430
+ * Validate page SEO metadata
431
+ */
432
+ static validateSeo(expected: { title?: string; description?: string }) {
433
+ if (expected.title) {
434
+ this.assertMetaTitle(expected.title)
435
+ this.assertOgTitle(expected.title)
436
+ }
437
+
438
+ if (expected.description) {
439
+ this.assertMetaDescription(expected.description)
440
+ this.assertOgDescription(expected.description)
441
+ }
442
+
443
+ return this
444
+ }
445
+ }
446
+
447
+ export default PublicPagePOM
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Public Post POM
3
+ *
4
+ * Page Object Model for public post display pages (/blog/[slug]).
5
+ * Covers post rendering, blocks, categories, and SEO verification.
6
+ *
7
+ * Convention: post-{component}-{element}
8
+ * Based on selectors documented in tests.md
9
+ */
10
+
11
+ export class PublicPostPOM {
12
+ // ============================================
13
+ // SELECTORS - Public Post
14
+ // ============================================
15
+
16
+ static get postSelectors() {
17
+ return {
18
+ content: '[data-cy="post-content"]',
19
+ header: '[data-cy="post-header"]',
20
+ title: '[data-cy="post-title"]',
21
+ excerpt: '[data-cy="post-excerpt"]',
22
+ featuredImage: '[data-cy="post-featured-image-display"]',
23
+ categoriesDisplay: '[data-cy="post-categories-display"]',
24
+ categoryBadge: (name: string) => `[data-cy="post-categories-display"] :contains("${name}")`,
25
+ }
26
+ }
27
+
28
+ // ============================================
29
+ // NAVIGATION
30
+ // ============================================
31
+
32
+ static visit(slug: string, options?: Partial<Cypress.VisitOptions>) {
33
+ cy.visit(`/blog/${slug}`, options)
34
+ return this
35
+ }
36
+
37
+ // ============================================
38
+ // WAIT METHODS
39
+ // ============================================
40
+
41
+ static waitForPostLoad() {
42
+ cy.get(this.postSelectors.content, { timeout: 15000 }).should('be.visible')
43
+ return this
44
+ }
45
+
46
+ // ============================================
47
+ // ASSERTIONS
48
+ // ============================================
49
+
50
+ static assertPostVisible() {
51
+ cy.get(this.postSelectors.content).should('be.visible')
52
+ return this
53
+ }
54
+
55
+ static assertHeaderVisible() {
56
+ cy.get(this.postSelectors.header).should('be.visible')
57
+ return this
58
+ }
59
+
60
+ static assertTitle(title: string) {
61
+ cy.get(this.postSelectors.title).should('contain', title)
62
+ return this
63
+ }
64
+
65
+ static assertExcerpt(excerpt: string) {
66
+ cy.get(this.postSelectors.excerpt).should('contain', excerpt)
67
+ return this
68
+ }
69
+
70
+ static assertFeaturedImageVisible() {
71
+ cy.get(this.postSelectors.featuredImage).should('be.visible')
72
+ return this
73
+ }
74
+
75
+ static assertCategoryVisible(categoryName: string) {
76
+ cy.get(this.postSelectors.categoriesDisplay).should('contain', categoryName)
77
+ return this
78
+ }
79
+
80
+ static assertCategoryBadgeColor(categoryName: string, color: string) {
81
+ // Check that category badge has the expected color in its styling
82
+ cy.get(this.postSelectors.categoryBadge(categoryName))
83
+ .should('exist')
84
+ .and('have.attr', 'style')
85
+ .and('include', color)
86
+ return this
87
+ }
88
+
89
+ static assertBlockRendered(blockType: string) {
90
+ // Check for block rendering (blocks typically have a wrapper with block-specific data)
91
+ cy.get(this.postSelectors.content).within(() => {
92
+ cy.contains(blockType, { matchCase: false }).should('exist')
93
+ })
94
+ return this
95
+ }
96
+
97
+ static assert404() {
98
+ cy.contains('404').should('be.visible')
99
+ return this
100
+ }
101
+
102
+ static assertUnpublishedNotAccessible() {
103
+ // Should redirect to 404 or show error
104
+ cy.url().should('match', /404|not-found/)
105
+ return this
106
+ }
107
+
108
+ // ============================================
109
+ // SEO VERIFICATION
110
+ // ============================================
111
+
112
+ static assertMetaTitle(title: string) {
113
+ cy.get('head title').should('contain', title)
114
+ return this
115
+ }
116
+
117
+ static assertMetaDescription(description: string) {
118
+ cy.get('head meta[name="description"]')
119
+ .should('have.attr', 'content')
120
+ .and('include', description)
121
+ return this
122
+ }
123
+
124
+ static assertOgImage(imageUrl: string) {
125
+ cy.get('head meta[property="og:image"]')
126
+ .should('have.attr', 'content')
127
+ .and('include', imageUrl)
128
+ return this
129
+ }
130
+
131
+ static assertNoIndex() {
132
+ cy.get('head meta[name="robots"]')
133
+ .should('have.attr', 'content')
134
+ .and('include', 'noindex')
135
+ return this
136
+ }
137
+
138
+ static assertNoFollow() {
139
+ cy.get('head meta[name="robots"]')
140
+ .should('have.attr', 'content')
141
+ .and('include', 'nofollow')
142
+ return this
143
+ }
144
+ }
145
+
146
+ export default PublicPostPOM