@nextsparkjs/theme-default 0.1.0-beta.17 → 0.1.0-beta.171

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 (332) hide show
  1. package/api/ai/chat/stream/route.ts +4 -1
  2. package/api/ai/orchestrator/route.ts +10 -3
  3. package/api/ai/single-agent/route.ts +10 -3
  4. package/api/ai/usage/route.ts +4 -1
  5. package/blocks/benefits/component.tsx +4 -4
  6. package/blocks/cta-section/component.tsx +4 -4
  7. package/blocks/faq-accordion/component.tsx +2 -2
  8. package/blocks/features-grid/component.tsx +5 -5
  9. package/blocks/hero/component.tsx +2 -2
  10. package/blocks/hero/fields.ts +1 -1
  11. package/blocks/hero-with-form/component.tsx +7 -7
  12. package/blocks/hero-with-form/fields.ts +1 -1
  13. package/blocks/jumbotron/component.tsx +7 -7
  14. package/blocks/jumbotron/fields.ts +1 -1
  15. package/blocks/logo-cloud/component.tsx +6 -6
  16. package/blocks/logo-cloud/fields.ts +1 -1
  17. package/blocks/post-content/component.tsx +2 -2
  18. package/blocks/pricing-table/component.tsx +5 -5
  19. package/blocks/split-content/component.tsx +5 -5
  20. package/blocks/split-content/fields.ts +1 -1
  21. package/blocks/stats-counter/component.tsx +9 -9
  22. package/blocks/testimonials/component.tsx +4 -4
  23. package/blocks/testimonials/fields.ts +1 -1
  24. package/blocks/text-content/component.tsx +12 -10
  25. package/blocks/timeline/component.tsx +12 -12
  26. package/blocks/video-hero/component.tsx +7 -7
  27. package/blocks/video-hero/fields.ts +1 -1
  28. package/components/ai-chat/ChatPanel.tsx +7 -7
  29. package/components/ai-chat/Message.tsx +2 -2
  30. package/components/ai-chat/MessageInput.tsx +3 -3
  31. package/components/ai-chat/MessageList.tsx +3 -3
  32. package/components/ai-chat/TypingIndicator.tsx +2 -2
  33. package/config/app.config.ts +90 -62
  34. package/config/billing.config.ts +5 -10
  35. package/config/dashboard.config.ts +14 -0
  36. package/config/features.config.ts +10 -0
  37. package/config/permissions.config.ts +22 -23
  38. package/docs/{01-overview → public/01-overview}/01-introduction.md +5 -0
  39. package/docs/{01-overview → public/01-overview}/02-customization.md +5 -0
  40. package/docs/{02-features → public/02-features}/03-tasks-entity.md +5 -0
  41. package/docs/{03-ai → public/03-ai}/01-overview.md +5 -0
  42. package/docs/{03-ai → public/03-ai}/02-customization.md +5 -0
  43. package/docs/superadmin/01-setup/01-configuration.md +79 -0
  44. package/docs/superadmin/01-setup/02-deployment.md +82 -0
  45. package/docs/superadmin/02-management/01-users.md +83 -0
  46. package/docs/superadmin/03-integrations/01-langchain.md +139 -0
  47. package/emails/verify-email.ts +33 -0
  48. package/entities/customers/api/docs.md +107 -0
  49. package/entities/customers/api/presets.ts +80 -0
  50. package/entities/pages/api/docs.md +114 -0
  51. package/entities/pages/api/presets.ts +72 -0
  52. package/entities/posts/api/docs.md +120 -0
  53. package/entities/posts/api/presets.ts +74 -0
  54. package/entities/tasks/api/docs.md +126 -0
  55. package/entities/tasks/api/presets.ts +84 -0
  56. package/lib/selectors.ts +7 -4
  57. package/messages/de/admin.json +45 -0
  58. package/messages/en/admin.json +56 -0
  59. package/messages/en/navigation.json +2 -1
  60. package/messages/es/admin.json +56 -0
  61. package/messages/es/navigation.json +2 -1
  62. package/messages/fr/admin.json +45 -0
  63. package/messages/it/admin.json +45 -0
  64. package/messages/pt/admin.json +45 -0
  65. package/migrations/089_add_editor_team_role.sql +15 -23
  66. package/migrations/090_demo_users_teams.sql +11 -11
  67. package/migrations/091_greek_teams_billing.sql +15 -15
  68. package/migrations/093_pages_sample_data.sql +7 -7
  69. package/migrations/098_patterns_sample_data.sql +234 -0
  70. package/nextsparkjs-theme-default-0.1.0-beta.137.tgz +0 -0
  71. package/package.json +4 -3
  72. package/styles/globals.css +42 -0
  73. package/templates/(public)/page.tsx +1 -1
  74. package/tests/cypress/e2e/_utils/devtools/access.bdd.md +262 -0
  75. package/tests/cypress/e2e/_utils/devtools/access.cy.ts +171 -0
  76. package/tests/cypress/e2e/_utils/devtools/navigation.bdd.md +261 -0
  77. package/tests/cypress/e2e/_utils/devtools/navigation.cy.ts +157 -0
  78. package/tests/cypress/e2e/_utils/devtools/pages.bdd.md +303 -0
  79. package/tests/cypress/e2e/_utils/devtools/pages.cy.ts +184 -0
  80. package/tests/cypress/e2e/_utils/docs/README.md +215 -0
  81. package/tests/cypress/e2e/_utils/selectors/auth.bdd.md +354 -0
  82. package/tests/cypress/e2e/_utils/selectors/auth.cy.ts +310 -0
  83. package/tests/cypress/e2e/_utils/selectors/billing.bdd.md +276 -0
  84. package/tests/cypress/e2e/_utils/selectors/billing.cy.ts +182 -0
  85. package/tests/cypress/e2e/_utils/selectors/block-editor.bdd.md +615 -0
  86. package/tests/cypress/e2e/_utils/selectors/block-editor.cy.ts +783 -0
  87. package/tests/cypress/e2e/_utils/selectors/dashboard-container.cy.ts +52 -0
  88. package/tests/cypress/e2e/_utils/selectors/dashboard-mobile.bdd.md +205 -0
  89. package/tests/cypress/e2e/_utils/selectors/dashboard-mobile.cy.ts +137 -0
  90. package/tests/cypress/e2e/_utils/selectors/dashboard-navigation.bdd.md +147 -0
  91. package/tests/cypress/e2e/_utils/selectors/dashboard-navigation.cy.ts +114 -0
  92. package/tests/cypress/e2e/_utils/selectors/dashboard-sidebar.bdd.md +76 -0
  93. package/tests/cypress/e2e/_utils/selectors/dashboard-sidebar.cy.ts +68 -0
  94. package/tests/cypress/e2e/_utils/selectors/dashboard-topnav.bdd.md +326 -0
  95. package/tests/cypress/e2e/_utils/selectors/dashboard-topnav.cy.ts +177 -0
  96. package/tests/cypress/e2e/_utils/selectors/devtools.bdd.md +306 -0
  97. package/tests/cypress/e2e/_utils/selectors/devtools.cy.ts +273 -0
  98. package/tests/cypress/e2e/_utils/selectors/global-search.bdd.md +115 -0
  99. package/tests/cypress/e2e/_utils/selectors/global-search.cy.ts +93 -0
  100. package/tests/cypress/e2e/_utils/selectors/patterns.bdd.md +388 -0
  101. package/tests/cypress/e2e/_utils/selectors/patterns.cy.ts +559 -0
  102. package/tests/cypress/e2e/_utils/selectors/public.cy.ts +112 -0
  103. package/tests/cypress/e2e/_utils/selectors/settings-api-keys.bdd.md +266 -0
  104. package/tests/cypress/e2e/_utils/selectors/settings-api-keys.cy.ts +233 -0
  105. package/tests/cypress/e2e/_utils/selectors/settings-billing.bdd.md +78 -0
  106. package/tests/cypress/e2e/_utils/selectors/settings-billing.cy.ts +108 -0
  107. package/tests/cypress/e2e/_utils/selectors/settings-layout.bdd.md +129 -0
  108. package/tests/cypress/e2e/_utils/selectors/settings-layout.cy.ts +115 -0
  109. package/tests/cypress/e2e/_utils/selectors/settings-password.bdd.md +82 -0
  110. package/tests/cypress/e2e/_utils/selectors/settings-password.cy.ts +74 -0
  111. package/tests/cypress/e2e/_utils/selectors/settings-profile.bdd.md +77 -0
  112. package/tests/cypress/e2e/_utils/selectors/settings-profile.cy.ts +79 -0
  113. package/tests/cypress/e2e/_utils/selectors/settings-teams.bdd.md +130 -0
  114. package/tests/cypress/e2e/_utils/selectors/settings-teams.cy.ts +86 -0
  115. package/tests/cypress/e2e/_utils/selectors/superadmin.bdd.md +261 -0
  116. package/tests/cypress/e2e/_utils/selectors/superadmin.cy.ts +193 -0
  117. package/tests/cypress/e2e/_utils/selectors/tasks.bdd.md +593 -0
  118. package/tests/cypress/e2e/_utils/selectors/tasks.cy.ts +864 -0
  119. package/tests/cypress/e2e/_utils/selectors/taxonomies.cy.ts +126 -0
  120. package/tests/cypress/e2e/_utils/selectors/teams.bdd.md +278 -0
  121. package/tests/cypress/e2e/_utils/selectors/teams.cy.ts +195 -0
  122. package/tests/cypress/e2e/_utils/superadmin/all-teams.bdd.md +261 -0
  123. package/tests/cypress/e2e/_utils/superadmin/all-teams.cy.ts +177 -0
  124. package/tests/cypress/e2e/_utils/superadmin/all-users.bdd.md +406 -0
  125. package/tests/cypress/e2e/_utils/superadmin/all-users.cy.ts +294 -0
  126. package/tests/cypress/e2e/_utils/superadmin/dashboard.bdd.md +235 -0
  127. package/tests/cypress/e2e/_utils/superadmin/dashboard.cy.ts +149 -0
  128. package/tests/cypress/e2e/_utils/superadmin/subscriptions-overview.bdd.md +290 -0
  129. package/tests/cypress/e2e/_utils/superadmin/subscriptions-overview.cy.ts +194 -0
  130. package/tests/cypress/e2e/ai/ai-usage.cy.ts +209 -0
  131. package/tests/cypress/e2e/ai/chat-api.cy.ts +119 -0
  132. package/tests/cypress/e2e/ai/guardrails.cy.ts +332 -0
  133. package/tests/cypress/e2e/api/_core/billing/BillingAPIController.js +319 -0
  134. package/tests/cypress/e2e/api/_core/billing/check-action.cy.ts +326 -0
  135. package/tests/cypress/e2e/api/_core/billing/checkout.cy.ts +358 -0
  136. package/tests/cypress/e2e/api/_core/billing/lifecycle.cy.ts +423 -0
  137. package/tests/cypress/e2e/api/_core/billing/plans/README.md +345 -0
  138. package/tests/cypress/e2e/api/_core/billing/plans/business.cy.ts +412 -0
  139. package/tests/cypress/e2e/api/_core/billing/plans/downgrade.cy.ts +510 -0
  140. package/tests/cypress/e2e/api/_core/billing/plans/fixtures/billing-plans.json +163 -0
  141. package/tests/cypress/e2e/api/_core/billing/plans/free.cy.ts +500 -0
  142. package/tests/cypress/e2e/api/_core/billing/plans/pro.cy.ts +497 -0
  143. package/tests/cypress/e2e/api/_core/billing/plans/starter.cy.ts +342 -0
  144. package/tests/cypress/e2e/api/_core/billing/portal.cy.ts +313 -0
  145. package/tests/cypress/e2e/api/_core/devtools/registries.bdd.md +300 -0
  146. package/tests/cypress/e2e/api/_core/devtools/registries.cy.ts +368 -0
  147. package/tests/cypress/e2e/api/_core/scheduled-actions/cron-endpoint.bdd.md +375 -0
  148. package/tests/cypress/e2e/api/_core/scheduled-actions/cron-endpoint.cy.ts +346 -0
  149. package/tests/cypress/e2e/api/_core/scheduled-actions/devtools-endpoint.bdd.md +451 -0
  150. package/tests/cypress/e2e/api/_core/scheduled-actions/devtools-endpoint.cy.ts +447 -0
  151. package/tests/cypress/e2e/api/_core/scheduled-actions/scheduling.bdd.md +649 -0
  152. package/tests/cypress/e2e/api/_core/scheduled-actions/scheduling.cy.ts +333 -0
  153. package/tests/cypress/e2e/api/_core/security/security-headers.cy.ts +601 -0
  154. package/tests/cypress/e2e/api/_core/settings/api-keys.crud.cy.ts +923 -0
  155. package/tests/cypress/e2e/api/_core/teams/teams-security.cy.ts +415 -0
  156. package/tests/cypress/e2e/api/_core/users/users-crud.cy.ts +469 -0
  157. package/tests/cypress/e2e/api/_core/users/users-metas.cy.ts +913 -0
  158. package/tests/cypress/e2e/api/_core/users/users-security.cy.ts +375 -0
  159. package/tests/cypress/e2e/api/entities/customers/customers-crud.cy.ts +648 -0
  160. package/tests/cypress/e2e/api/entities/customers/customers-metas.cy.ts +839 -0
  161. package/tests/cypress/e2e/api/entities/media/media-crud.cy.ts +600 -0
  162. package/tests/cypress/e2e/api/entities/media/media-role-permissions.cy.ts +617 -0
  163. package/tests/cypress/e2e/api/entities/media/media-team-isolation.cy.ts +464 -0
  164. package/tests/cypress/e2e/api/entities/pages/blocks-scope.cy.ts +396 -0
  165. package/tests/cypress/e2e/api/entities/pages/pages-crud.cy.ts +425 -0
  166. package/tests/cypress/e2e/api/entities/pages/pages-status.cy.ts +335 -0
  167. package/tests/cypress/e2e/api/entities/posts/post-categories-crud.cy.ts +610 -0
  168. package/tests/cypress/e2e/api/entities/posts/posts-crud.cy.ts +709 -0
  169. package/tests/cypress/e2e/api/entities/posts/posts-status.cy.ts +396 -0
  170. package/tests/cypress/e2e/api/entities/tasks/tasks-crud.cy.ts +602 -0
  171. package/tests/cypress/e2e/api/entities/tasks/tasks-metas.cy.ts +878 -0
  172. package/tests/cypress/e2e/patterns/patterns-in-pages.cy.ts +367 -0
  173. package/tests/cypress/e2e/uat/_core/auth/app-roles/developer-login.bdd.md +231 -0
  174. package/tests/cypress/e2e/uat/_core/auth/app-roles/developer-login.cy.ts +144 -0
  175. package/tests/cypress/e2e/uat/_core/auth/app-roles/superadmin-login.bdd.md +118 -0
  176. package/tests/cypress/e2e/uat/_core/auth/app-roles/superadmin-login.cy.ts +84 -0
  177. package/tests/cypress/e2e/uat/_core/auth/custom-roles/editor-login.bdd.md +288 -0
  178. package/tests/cypress/e2e/uat/_core/auth/custom-roles/editor-login.cy.ts +188 -0
  179. package/tests/cypress/e2e/uat/_core/auth/login-logout.bdd.md +160 -0
  180. package/tests/cypress/e2e/uat/_core/auth/login-logout.cy.ts +116 -0
  181. package/tests/cypress/e2e/uat/_core/auth/password-reset.bdd.md +289 -0
  182. package/tests/cypress/e2e/uat/_core/auth/password-reset.cy.ts +200 -0
  183. package/tests/cypress/e2e/uat/_core/auth/registration-control-invitation.cy.ts +176 -0
  184. package/tests/cypress/e2e/uat/_core/auth/registration-control-open.cy.ts +131 -0
  185. package/tests/cypress/e2e/uat/_core/auth/registration-control.cy.ts +140 -0
  186. package/tests/cypress/e2e/uat/_core/auth/team-roles/admin-login.bdd.md +225 -0
  187. package/tests/cypress/e2e/uat/_core/auth/team-roles/admin-login.cy.ts +148 -0
  188. package/tests/cypress/e2e/uat/_core/auth/team-roles/member-login.bdd.md +251 -0
  189. package/tests/cypress/e2e/uat/_core/auth/team-roles/member-login.cy.ts +163 -0
  190. package/tests/cypress/e2e/uat/_core/auth/team-roles/owner-login.bdd.md +231 -0
  191. package/tests/cypress/e2e/uat/_core/auth/team-roles/owner-login.cy.ts +141 -0
  192. package/tests/cypress/e2e/uat/_core/billing/extended.bdd.md +273 -0
  193. package/tests/cypress/e2e/uat/_core/billing/extended.cy.ts +209 -0
  194. package/tests/cypress/e2e/uat/_core/billing/feature-gates.bdd.md +407 -0
  195. package/tests/cypress/e2e/uat/_core/billing/feature-gates.cy.ts +307 -0
  196. package/tests/cypress/e2e/uat/_core/billing/page.bdd.md +329 -0
  197. package/tests/cypress/e2e/uat/_core/billing/page.cy.ts +250 -0
  198. package/tests/cypress/e2e/uat/_core/billing/status.bdd.md +190 -0
  199. package/tests/cypress/e2e/uat/_core/billing/status.cy.ts +145 -0
  200. package/tests/cypress/e2e/uat/_core/billing/team-switch.bdd.md +156 -0
  201. package/tests/cypress/e2e/uat/_core/billing/team-switch.cy.ts +122 -0
  202. package/tests/cypress/e2e/uat/_core/billing/usage.bdd.md +218 -0
  203. package/tests/cypress/e2e/uat/_core/billing/usage.cy.ts +176 -0
  204. package/tests/cypress/e2e/uat/_core/blocks/hero.bdd.md +124 -0
  205. package/tests/cypress/e2e/uat/_core/blocks/hero.cy.ts +56 -0
  206. package/tests/cypress/e2e/uat/_core/devtools/api-tester.cy.ts +390 -0
  207. package/tests/cypress/e2e/uat/_core/performance/suspense-loading.cy.ts +134 -0
  208. package/tests/cypress/e2e/uat/_core/scheduled-actions/devtools-ui.bdd.md +736 -0
  209. package/tests/cypress/e2e/uat/_core/scheduled-actions/devtools-ui.cy.ts +740 -0
  210. package/tests/cypress/e2e/uat/_core/teams/inline-edit.cy.ts +278 -0
  211. package/tests/cypress/e2e/uat/_core/teams/roles-matrix.bdd.md +553 -0
  212. package/tests/cypress/e2e/uat/_core/teams/roles-matrix.cy.ts +185 -0
  213. package/tests/cypress/e2e/uat/_core/teams/switcher.bdd.md +1151 -0
  214. package/tests/cypress/e2e/uat/_core/teams/switcher.cy.ts +497 -0
  215. package/tests/cypress/e2e/uat/_core/teams/team-switcher.md +198 -0
  216. package/tests/cypress/e2e/uat/entities/customers/member.bdd.md +275 -0
  217. package/tests/cypress/e2e/uat/entities/customers/member.cy.ts +122 -0
  218. package/tests/cypress/e2e/uat/entities/customers/owner.bdd.md +243 -0
  219. package/tests/cypress/e2e/uat/entities/customers/owner.cy.ts +165 -0
  220. package/tests/cypress/e2e/uat/entities/pages/block-crud.bdd.md +476 -0
  221. package/tests/cypress/e2e/uat/entities/pages/block-crud.cy.ts +486 -0
  222. package/tests/cypress/e2e/uat/entities/pages/block-editor.bdd.md +460 -0
  223. package/tests/cypress/e2e/uat/entities/pages/block-editor.cy.ts +301 -0
  224. package/tests/cypress/e2e/uat/entities/pages/list.bdd.md +432 -0
  225. package/tests/cypress/e2e/uat/entities/pages/list.cy.ts +273 -0
  226. package/tests/cypress/e2e/uat/entities/pages/public-rendering.bdd.md +696 -0
  227. package/tests/cypress/e2e/uat/entities/pages/public-rendering.cy.ts +340 -0
  228. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.bdd.md +161 -0
  229. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.cy.ts +104 -0
  230. package/tests/cypress/e2e/uat/entities/posts/categories.bdd.md +375 -0
  231. package/tests/cypress/e2e/uat/entities/posts/categories.cy.ts +241 -0
  232. package/tests/cypress/e2e/uat/entities/posts/editor.bdd.md +429 -0
  233. package/tests/cypress/e2e/uat/entities/posts/editor.cy.ts +257 -0
  234. package/tests/cypress/e2e/uat/entities/posts/list.bdd.md +340 -0
  235. package/tests/cypress/e2e/uat/entities/posts/list.cy.ts +177 -0
  236. package/tests/cypress/e2e/uat/entities/posts/public.bdd.md +614 -0
  237. package/tests/cypress/e2e/uat/entities/posts/public.cy.ts +249 -0
  238. package/tests/cypress/e2e/uat/entities/tasks/member.bdd.md +222 -0
  239. package/tests/cypress/e2e/uat/entities/tasks/member.cy.ts +165 -0
  240. package/tests/cypress/e2e/uat/entities/tasks/owner.bdd.md +419 -0
  241. package/tests/cypress/e2e/uat/entities/tasks/owner.cy.ts +191 -0
  242. package/tests/cypress/e2e/uat/features/roles/editor-role.bdd.md +552 -0
  243. package/tests/cypress/e2e/uat/features/roles/editor-role.cy.ts +210 -0
  244. package/tests/cypress/e2e/uat/features/roles/member-restrictions.bdd.md +450 -0
  245. package/tests/cypress/e2e/uat/features/roles/member-restrictions.cy.ts +189 -0
  246. package/tests/cypress/e2e/uat/features/roles/owner-full-crud.bdd.md +530 -0
  247. package/tests/cypress/e2e/uat/features/roles/owner-full-crud.cy.ts +247 -0
  248. package/tests/cypress/fixtures/blocks.json +218 -0
  249. package/tests/cypress/fixtures/entities.json +87 -0
  250. package/tests/cypress/fixtures/page-builder.json +21 -0
  251. package/tests/cypress/src/components/CategoriesPOM.ts +382 -0
  252. package/tests/cypress/src/components/CustomersPOM.ts +439 -0
  253. package/tests/cypress/src/components/DevKeyringPOM.ts +160 -0
  254. package/tests/cypress/src/components/EntityForm.ts +375 -0
  255. package/tests/cypress/src/components/EntityList.ts +389 -0
  256. package/tests/cypress/src/components/PageBuilderPOM.ts +710 -0
  257. package/tests/cypress/src/components/PostEditorPOM.ts +370 -0
  258. package/tests/cypress/src/components/PostsListPOM.ts +223 -0
  259. package/tests/cypress/src/components/PublicPagePOM.ts +447 -0
  260. package/tests/cypress/src/components/PublicPostPOM.ts +146 -0
  261. package/tests/cypress/src/components/TasksPOM.ts +272 -0
  262. package/tests/cypress/src/components/TeamSwitcherPOM.ts +450 -0
  263. package/tests/cypress/src/components/index.ts +21 -0
  264. package/tests/cypress/src/controllers/ApiKeysAPIController.js +178 -0
  265. package/tests/cypress/src/controllers/BaseAPIController.js +317 -0
  266. package/tests/cypress/src/controllers/CustomerAPIController.js +251 -0
  267. package/tests/cypress/src/controllers/MediaAPIController.js +231 -0
  268. package/tests/cypress/src/controllers/PagesAPIController.js +226 -0
  269. package/tests/cypress/src/controllers/PostsAPIController.js +250 -0
  270. package/tests/cypress/src/controllers/TaskAPIController.js +240 -0
  271. package/tests/cypress/src/controllers/UsersAPIController.js +242 -0
  272. package/tests/cypress/src/controllers/index.js +25 -0
  273. package/tests/cypress/src/core/AuthPOM.ts +450 -0
  274. package/tests/cypress/src/core/BasePOM.ts +33 -0
  275. package/tests/cypress/src/core/BlockEditorBasePOM.ts +874 -0
  276. package/tests/cypress/src/core/DashboardEntityPOM.ts +41 -0
  277. package/tests/cypress/src/core/index.ts +14 -0
  278. package/tests/cypress/src/entities/CustomersPOM.ts +172 -0
  279. package/tests/cypress/src/entities/PagesPOM.ts +137 -0
  280. package/tests/cypress/src/entities/PatternsPOM.ts +329 -0
  281. package/tests/cypress/src/entities/PostsPOM.ts +137 -0
  282. package/tests/cypress/src/entities/TasksPOM.ts +246 -0
  283. package/tests/cypress/src/entities/index.ts +16 -0
  284. package/tests/cypress/src/features/BillingPOM.ts +385 -0
  285. package/tests/cypress/src/features/DashboardPOM.ts +271 -0
  286. package/tests/cypress/src/features/DevtoolsPOM.ts +750 -0
  287. package/tests/cypress/src/features/PageBuilderPOM.ts +283 -0
  288. package/tests/cypress/src/features/PostEditorPOM.ts +313 -0
  289. package/tests/cypress/src/features/ScheduledActionsPOM.ts +463 -0
  290. package/tests/cypress/src/features/SettingsPOM.ts +707 -0
  291. package/tests/cypress/src/features/SuperadminPOM.ts +851 -0
  292. package/tests/cypress/src/features/SuperadminTeamRolesPOM.ts +285 -0
  293. package/tests/cypress/src/features/index.ts +28 -0
  294. package/tests/cypress/src/helpers/ApiInterceptor.ts +20 -0
  295. package/tests/cypress/src/index.ts +101 -0
  296. package/tests/cypress/src/pages/dashboard/Dashboard.js +677 -0
  297. package/tests/cypress/src/pages/dashboard/DashboardPage.js +43 -0
  298. package/tests/cypress/src/pages/dashboard/DashboardStats.js +546 -0
  299. package/tests/cypress/src/pages/dashboard/index.js +6 -0
  300. package/tests/cypress/src/pages/index.js +5 -0
  301. package/tests/cypress/src/pages/public/FeaturesPage.js +28 -0
  302. package/tests/cypress/src/pages/public/LandingPage.js +69 -0
  303. package/tests/cypress/src/pages/public/PricingPage.js +33 -0
  304. package/tests/cypress/src/pages/public/index.js +6 -0
  305. package/tests/cypress/src/selectors.ts +46 -0
  306. package/tests/cypress/src/session-helpers.ts +518 -0
  307. package/tests/cypress/support/doc-commands.ts +260 -0
  308. package/tests/cypress/support/e2e.ts +90 -0
  309. package/tests/cypress.config.ts +178 -0
  310. package/tests/jest/__mocks__/@nextsparkjs/core/components/ui/badge.js +16 -0
  311. package/tests/jest/__mocks__/@nextsparkjs/core/lib/db.js +11 -0
  312. package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +160 -0
  313. package/tests/jest/__mocks__/@nextsparkjs/registries/theme-registry.ts +68 -0
  314. package/tests/jest/__mocks__/jose.js +22 -0
  315. package/tests/jest/__mocks__/next/image.js +15 -0
  316. package/tests/jest/__mocks__/next-server.js +56 -0
  317. package/tests/jest/components/post-header.test.tsx +377 -0
  318. package/tests/jest/jest.config.cjs +154 -0
  319. package/tests/jest/langchain/COVERAGE.md +372 -0
  320. package/tests/jest/langchain/guardrails.test.ts +465 -0
  321. package/tests/jest/langchain/streaming.test.ts +370 -0
  322. package/tests/jest/langchain/token-tracker.test.ts +455 -0
  323. package/tests/jest/langchain/tracer-callbacks.test.ts +881 -0
  324. package/tests/jest/langchain/tracer.test.ts +823 -0
  325. package/tests/jest/services/tasks.service.test.ts +707 -0
  326. package/tests/jest/setup.ts +170 -0
  327. package/tests/jest/tsconfig.jest.json +6 -0
  328. package/tests/jest/validation/categories.test.ts +429 -0
  329. package/tests/jest/validation/posts.test.ts +546 -0
  330. package/tests/tsconfig.json +21 -0
  331. /package/docs/{02-features → public/02-features}/01-components.md +0 -0
  332. /package/docs/{02-features → public/02-features}/02-styling.md +0 -0
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Mock Permissions Registry for Jest tests
3
+ */
4
+
5
+ export type Permission = string
6
+
7
+ export interface ResolvedPermission {
8
+ id: string
9
+ label: string
10
+ description: string
11
+ category: string
12
+ roles: string[]
13
+ dangerous: boolean
14
+ source: 'core' | 'theme'
15
+ disabled?: boolean
16
+ }
17
+
18
+ export interface PermissionUISection {
19
+ id: string
20
+ label: string
21
+ description: string
22
+ categories: string[]
23
+ }
24
+
25
+ export interface RolesConfig {
26
+ additionalRoles?: string[]
27
+ hierarchy?: Record<string, number>
28
+ displayNames?: Record<string, string>
29
+ descriptions?: Record<string, string>
30
+ }
31
+
32
+ // Mock permissions data
33
+ export const ALL_RESOLVED_PERMISSIONS: ResolvedPermission[] = [
34
+ {
35
+ id: 'tasks.create',
36
+ label: 'Create tasks',
37
+ description: 'Can create new tasks',
38
+ category: 'Tasks',
39
+ roles: ['owner', 'admin', 'member'],
40
+ dangerous: false,
41
+ source: 'theme',
42
+ },
43
+ {
44
+ id: 'tasks.read',
45
+ label: 'View tasks',
46
+ description: 'Can view task details',
47
+ category: 'Tasks',
48
+ roles: ['owner', 'admin', 'member'],
49
+ dangerous: false,
50
+ source: 'theme',
51
+ },
52
+ {
53
+ id: 'tasks.update',
54
+ label: 'Edit tasks',
55
+ description: 'Can modify task information',
56
+ category: 'Tasks',
57
+ roles: ['owner', 'admin', 'member'],
58
+ dangerous: false,
59
+ source: 'theme',
60
+ },
61
+ {
62
+ id: 'tasks.delete',
63
+ label: 'Delete tasks',
64
+ description: 'Can delete tasks',
65
+ category: 'Tasks',
66
+ roles: ['owner', 'admin'],
67
+ dangerous: true,
68
+ source: 'theme',
69
+ },
70
+ ]
71
+
72
+ export const ALL_PERMISSIONS: Permission[] = ALL_RESOLVED_PERMISSIONS.map(p => p.id)
73
+
74
+ export const ALL_PERMISSIONS_SET = new Set(ALL_PERMISSIONS)
75
+
76
+ export const AVAILABLE_ROLES: readonly string[] = ['owner', 'admin', 'editor', 'member', 'viewer']
77
+
78
+ export const ROLE_HIERARCHY: Record<string, number> = {
79
+ owner: 100,
80
+ admin: 50,
81
+ editor: 30,
82
+ member: 10,
83
+ viewer: 1,
84
+ }
85
+
86
+ export const ROLE_DISPLAY_NAMES: Record<string, string> = {
87
+ owner: 'common.teamRoles.owner',
88
+ admin: 'common.teamRoles.admin',
89
+ editor: 'common.teamRoles.editor',
90
+ member: 'common.teamRoles.member',
91
+ viewer: 'common.teamRoles.viewer',
92
+ }
93
+
94
+ export const ROLE_DESCRIPTIONS: Record<string, string> = {
95
+ owner: 'Full team control, cannot be removed',
96
+ admin: 'Manage team members and settings',
97
+ editor: 'Can edit content but not manage team',
98
+ member: 'Standard team access',
99
+ viewer: 'Read-only access to team resources',
100
+ }
101
+
102
+ export const CUSTOM_ROLES: RolesConfig = {
103
+ additionalRoles: ['editor'],
104
+ hierarchy: { editor: 30 },
105
+ displayNames: { editor: 'common.teamRoles.editor' },
106
+ descriptions: { editor: 'Can edit content but not manage team' },
107
+ }
108
+
109
+ export const PERMISSIONS_BY_ROLE: Record<string, Set<Permission>> = {
110
+ owner: new Set(ALL_PERMISSIONS),
111
+ admin: new Set(['tasks.create', 'tasks.read', 'tasks.update', 'tasks.delete']),
112
+ editor: new Set(['tasks.create', 'tasks.read', 'tasks.update']),
113
+ member: new Set(['tasks.create', 'tasks.read', 'tasks.update']),
114
+ viewer: new Set(['tasks.read']),
115
+ }
116
+
117
+ export const ROLE_PERMISSIONS_ARRAY: Record<string, Permission[]> = {
118
+ owner: [...ALL_PERMISSIONS],
119
+ admin: ['tasks.create', 'tasks.read', 'tasks.update', 'tasks.delete'],
120
+ editor: ['tasks.create', 'tasks.read', 'tasks.update'],
121
+ member: ['tasks.create', 'tasks.read', 'tasks.update'],
122
+ viewer: ['tasks.read'],
123
+ }
124
+
125
+ export const PERMISSIONS_BY_CATEGORY: Record<string, ResolvedPermission[]> = {
126
+ Tasks: ALL_RESOLVED_PERMISSIONS,
127
+ }
128
+
129
+ export const FULL_MATRIX: Record<Permission, Record<string, boolean>> = {}
130
+ for (const perm of ALL_RESOLVED_PERMISSIONS) {
131
+ FULL_MATRIX[perm.id] = {}
132
+ for (const role of AVAILABLE_ROLES) {
133
+ FULL_MATRIX[perm.id][role] = PERMISSIONS_BY_ROLE[role]?.has(perm.id) ?? false
134
+ }
135
+ }
136
+
137
+ export const UI_SECTIONS: PermissionUISection[] = [
138
+ {
139
+ id: 'entities',
140
+ label: 'Entities',
141
+ description: 'Entity-specific permissions',
142
+ categories: ['Tasks'],
143
+ },
144
+ ]
145
+
146
+ export const TEAM_PERMISSIONS_RAW = []
147
+ export const TEAM_PERMISSIONS_BY_ROLE: Record<string, string[]> = {}
148
+
149
+ export const PERMISSIONS_METADATA = {
150
+ totalPermissions: ALL_PERMISSIONS.length,
151
+ corePermissions: 0,
152
+ teamPermissions: 0,
153
+ featurePermissions: 0,
154
+ entityPermissions: ALL_RESOLVED_PERMISSIONS.length,
155
+ customRoles: 0,
156
+ availableRoles: AVAILABLE_ROLES.length,
157
+ categories: Object.keys(PERMISSIONS_BY_CATEGORY).length,
158
+ generatedAt: new Date().toISOString(),
159
+ theme: 'default',
160
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Mock Theme Registry for Jest tests
3
+ */
4
+
5
+ export interface ThemeEntity {
6
+ name: string
7
+ source: string
8
+ config: any
9
+ }
10
+
11
+ export interface ThemeRouteFile {
12
+ path: string
13
+ type: 'api' | 'page'
14
+ methods?: string[]
15
+ }
16
+
17
+ export interface ThemeRegistryEntry {
18
+ name: string
19
+ slug: string
20
+ displayName: string
21
+ description: string
22
+ version: string
23
+ entities: ThemeEntity[]
24
+ routes: ThemeRouteFile[]
25
+ middlewares: any[]
26
+ plugins: string[]
27
+ path: string
28
+ }
29
+
30
+ export const THEME_REGISTRY: Record<string, ThemeRegistryEntry> = {
31
+ default: {
32
+ name: 'default',
33
+ slug: 'default',
34
+ displayName: 'Default Theme',
35
+ description: 'Default theme for NextSpark',
36
+ version: '1.0.0',
37
+ entities: [
38
+ { name: 'posts', source: 'default', config: { name: 'posts', label: 'Posts' } }
39
+ ],
40
+ routes: [
41
+ { path: '/api/v1/posts', type: 'api', methods: ['GET', 'POST'] }
42
+ ],
43
+ middlewares: [],
44
+ plugins: [],
45
+ path: 'contents/themes/default',
46
+ },
47
+ }
48
+
49
+ export type ThemeName = keyof typeof THEME_REGISTRY | string
50
+
51
+ export const THEME_METADATA = {
52
+ generated: new Date().toISOString(),
53
+ activeTheme: 'default',
54
+ totalThemes: Object.keys(THEME_REGISTRY).length,
55
+ totalThemeEntities: 1,
56
+ totalThemeRoutes: 1,
57
+ themes: Object.keys(THEME_REGISTRY),
58
+ }
59
+
60
+ // Helper function for getting active theme
61
+ export function getActiveTheme(): ThemeRegistryEntry | undefined {
62
+ return THEME_REGISTRY[THEME_METADATA.activeTheme]
63
+ }
64
+
65
+ // Helper function for getting theme by name
66
+ export function getTheme(name: string): ThemeRegistryEntry | undefined {
67
+ return THEME_REGISTRY[name]
68
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Mock for jose package
3
+ * Resolves ES module import issues in Jest tests
4
+ */
5
+
6
+ module.exports = {
7
+ jwtVerify: jest.fn(),
8
+ SignJWT: jest.fn().mockImplementation(() => ({
9
+ setProtectedHeader: jest.fn().mockReturnThis(),
10
+ setIssuedAt: jest.fn().mockReturnThis(),
11
+ setExpirationTime: jest.fn().mockReturnThis(),
12
+ sign: jest.fn().mockResolvedValue('mock-jwt-token')
13
+ })),
14
+ importJWK: jest.fn(),
15
+ generateSecret: jest.fn(),
16
+ createRemoteJWKSet: jest.fn(),
17
+ errors: {
18
+ JWTExpired: class JWTExpired extends Error {},
19
+ JWTInvalid: class JWTInvalid extends Error {},
20
+ JWTClaimValidationFailed: class JWTClaimValidationFailed extends Error {}
21
+ }
22
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Mock for next/image
3
+ */
4
+ const React = require('react')
5
+
6
+ const Image = ({ src, alt, ...props }) => {
7
+ return React.createElement('img', {
8
+ src: typeof src === 'object' ? src.src : src,
9
+ alt: alt || '',
10
+ ...props,
11
+ })
12
+ }
13
+
14
+ module.exports = Image
15
+ module.exports.default = Image
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Mock for next/server
3
+ * Provides NextRequest and NextResponse mocks for API testing
4
+ */
5
+
6
+ class MockNextRequest {
7
+ constructor(url, options = {}) {
8
+ this.url = url
9
+ this.method = options.method || 'GET'
10
+ this.headers = new Map(Object.entries(options.headers || {}))
11
+ this._body = options.body
12
+ }
13
+
14
+ async json() {
15
+ if (!this._body) return {}
16
+ try {
17
+ return typeof this._body === 'string' ? JSON.parse(this._body) : this._body
18
+ } catch {
19
+ throw new SyntaxError('Invalid JSON')
20
+ }
21
+ }
22
+
23
+ async text() {
24
+ return this._body || ''
25
+ }
26
+ }
27
+
28
+ class MockNextResponse {
29
+ constructor(body, options = {}) {
30
+ this.body = body
31
+ this.status = options.status || 200
32
+ this.statusText = options.statusText || 'OK'
33
+ this.headers = new Map(Object.entries(options.headers || {}))
34
+ }
35
+
36
+ async json() {
37
+ if (!this.body) return {}
38
+ try {
39
+ return typeof this.body === 'string' ? JSON.parse(this.body) : this.body
40
+ } catch {
41
+ throw new SyntaxError('Invalid JSON')
42
+ }
43
+ }
44
+
45
+ static json(data, options = {}) {
46
+ return new MockNextResponse(data, {
47
+ ...options,
48
+ headers: { 'Content-Type': 'application/json', ...options.headers }
49
+ })
50
+ }
51
+ }
52
+
53
+ module.exports = {
54
+ NextRequest: MockNextRequest,
55
+ NextResponse: MockNextResponse
56
+ }
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Unit Tests - PostHeader Component
3
+ *
4
+ * Tests PostHeader component logic and rendering:
5
+ * - Renders title, excerpt, and metadata
6
+ * - Handles optional fields (featuredImage, excerpt, categories)
7
+ * - Displays category badges with colors
8
+ * - Formats dates correctly
9
+ * - data-cy attribute presence
10
+ *
11
+ * Focus on component behavior WITHOUT full E2E interaction.
12
+ */
13
+
14
+ import React from 'react'
15
+ import { render, screen } from '@testing-library/react'
16
+ import { PostHeader } from '@/themes/default/entities/posts/components/post-header'
17
+
18
+ describe('PostHeader Component', () => {
19
+ const mockPost = {
20
+ title: 'Test Post Title',
21
+ excerpt: 'This is a test excerpt for the post',
22
+ featuredImage: 'https://example.com/image.jpg',
23
+ createdAt: '2024-12-16T10:00:00.000Z',
24
+ categories: [
25
+ { id: 'cat-1', name: 'Technology', color: '#3B82F6' },
26
+ { id: 'cat-2', name: 'Tutorials', color: '#10B981' }
27
+ ]
28
+ }
29
+
30
+ describe('Rendering - Complete Post', () => {
31
+ it('should render post title', () => {
32
+ render(<PostHeader post={mockPost} />)
33
+
34
+ const title = screen.getByText('Test Post Title')
35
+ expect(title).toBeInTheDocument()
36
+ expect(title).toHaveAttribute('data-cy', 'post-title')
37
+ })
38
+
39
+ it('should render post excerpt', () => {
40
+ render(<PostHeader post={mockPost} />)
41
+
42
+ const excerpt = screen.getByText('This is a test excerpt for the post')
43
+ expect(excerpt).toBeInTheDocument()
44
+ expect(excerpt).toHaveAttribute('data-cy', 'post-excerpt')
45
+ })
46
+
47
+ it('should render featured image', () => {
48
+ render(<PostHeader post={mockPost} />)
49
+
50
+ const image = screen.getByAltText('Test Post Title')
51
+ expect(image).toBeInTheDocument()
52
+ expect(image).toHaveAttribute('src', expect.stringContaining('image.jpg'))
53
+ })
54
+
55
+ it('should render all categories', () => {
56
+ render(<PostHeader post={mockPost} />)
57
+
58
+ expect(screen.getByText('Technology')).toBeInTheDocument()
59
+ expect(screen.getByText('Tutorials')).toBeInTheDocument()
60
+ })
61
+
62
+ it('should render formatted date', () => {
63
+ render(<PostHeader post={mockPost} />)
64
+
65
+ // Date should be formatted as "December 16, 2024"
66
+ const dateElement = screen.getByText(/December 16, 2024/i)
67
+ expect(dateElement).toBeInTheDocument()
68
+ })
69
+
70
+ it('should have main container with data-cy', () => {
71
+ const { container } = render(<PostHeader post={mockPost} />)
72
+
73
+ const header = container.querySelector('[data-cy="post-header"]')
74
+ expect(header).toBeInTheDocument()
75
+ })
76
+ })
77
+
78
+ describe('Rendering - Optional Fields', () => {
79
+ it('should not render featured image when missing', () => {
80
+ const postWithoutImage = {
81
+ ...mockPost,
82
+ featuredImage: undefined
83
+ }
84
+
85
+ const { container } = render(<PostHeader post={postWithoutImage} />)
86
+
87
+ const imageContainer = container.querySelector('[data-cy="post-featured-image-display"]')
88
+ expect(imageContainer).not.toBeInTheDocument()
89
+ })
90
+
91
+ it('should not render excerpt when missing', () => {
92
+ const postWithoutExcerpt = {
93
+ ...mockPost,
94
+ excerpt: undefined
95
+ }
96
+
97
+ render(<PostHeader post={postWithoutExcerpt} />)
98
+
99
+ const excerpt = screen.queryByTestId('post-excerpt')
100
+ expect(excerpt).not.toBeInTheDocument()
101
+ })
102
+
103
+ it('should not render categories section when empty', () => {
104
+ const postWithoutCategories = {
105
+ ...mockPost,
106
+ categories: []
107
+ }
108
+
109
+ const { container } = render(<PostHeader post={postWithoutCategories} />)
110
+
111
+ const categoriesContainer = container.querySelector('[data-cy="post-categories-display"]')
112
+ expect(categoriesContainer).not.toBeInTheDocument()
113
+ })
114
+
115
+ it('should not render categories section when undefined', () => {
116
+ const postWithoutCategories = {
117
+ ...mockPost,
118
+ categories: undefined
119
+ }
120
+
121
+ const { container } = render(<PostHeader post={postWithoutCategories} />)
122
+
123
+ const categoriesContainer = container.querySelector('[data-cy="post-categories-display"]')
124
+ expect(categoriesContainer).not.toBeInTheDocument()
125
+ })
126
+ })
127
+
128
+ describe('Category Badges', () => {
129
+ it('should render category badge with correct color', () => {
130
+ render(<PostHeader post={mockPost} />)
131
+
132
+ const techBadge = screen.getByText('Technology')
133
+
134
+ // Badge should have inline styles with the color
135
+ const parentBadge = techBadge.closest('[style]')
136
+ expect(parentBadge).toHaveStyle({
137
+ borderColor: '#3B82F6',
138
+ color: '#3B82F6'
139
+ })
140
+ })
141
+
142
+ it('should render category badge with background color opacity', () => {
143
+ render(<PostHeader post={mockPost} />)
144
+
145
+ const techBadge = screen.getByText('Technology')
146
+ const parentBadge = techBadge.closest('[style]')
147
+
148
+ // backgroundColor should be color + 20 (hex opacity)
149
+ expect(parentBadge).toHaveStyle({
150
+ backgroundColor: '#3B82F620'
151
+ })
152
+ })
153
+
154
+ it('should handle categories without color', () => {
155
+ const postWithNoColorCategory = {
156
+ ...mockPost,
157
+ categories: [
158
+ { id: 'cat-1', name: 'No Color Category' }
159
+ ]
160
+ }
161
+
162
+ render(<PostHeader post={postWithNoColorCategory} />)
163
+
164
+ const badge = screen.getByText('No Color Category')
165
+ expect(badge).toBeInTheDocument()
166
+
167
+ // Badge should still render without inline color styles
168
+ const parentBadge = badge.closest('[style]')
169
+ // If no color, styles will be undefined or not include custom colors
170
+ if (parentBadge) {
171
+ const style = parentBadge.getAttribute('style')
172
+ expect(style).not.toContain('borderColor')
173
+ }
174
+ })
175
+
176
+ it('should render multiple category badges', () => {
177
+ const postWithManyCategories = {
178
+ ...mockPost,
179
+ categories: [
180
+ { id: 'cat-1', name: 'Category 1', color: '#FF0000' },
181
+ { id: 'cat-2', name: 'Category 2', color: '#00FF00' },
182
+ { id: 'cat-3', name: 'Category 3', color: '#0000FF' }
183
+ ]
184
+ }
185
+
186
+ render(<PostHeader post={postWithManyCategories} />)
187
+
188
+ expect(screen.getByText('Category 1')).toBeInTheDocument()
189
+ expect(screen.getByText('Category 2')).toBeInTheDocument()
190
+ expect(screen.getByText('Category 3')).toBeInTheDocument()
191
+ })
192
+ })
193
+
194
+ describe('Date Formatting', () => {
195
+ it('should format date correctly for different dates', () => {
196
+ const testDates = [
197
+ { input: '2024-01-01T00:00:00.000Z', expected: 'January 1, 2024' },
198
+ { input: '2024-06-15T12:30:00.000Z', expected: 'June 15, 2024' },
199
+ { input: '2024-12-31T23:59:59.000Z', expected: 'December 31, 2024' }
200
+ ]
201
+
202
+ testDates.forEach(({ input, expected }) => {
203
+ const post = { ...mockPost, createdAt: input }
204
+ const { unmount } = render(<PostHeader post={post} />)
205
+
206
+ const dateElement = screen.getByText(new RegExp(expected, 'i'))
207
+ expect(dateElement).toBeInTheDocument()
208
+
209
+ unmount()
210
+ })
211
+ })
212
+
213
+ it('should include dateTime attribute on time element', () => {
214
+ render(<PostHeader post={mockPost} />)
215
+
216
+ const timeElement = screen.getByText(/December 16, 2024/i).closest('time')
217
+ expect(timeElement).toHaveAttribute('dateTime', '2024-12-16T10:00:00.000Z')
218
+ })
219
+ })
220
+
221
+ describe('Data-cy Attributes', () => {
222
+ it('should have data-cy on post header container', () => {
223
+ const { container } = render(<PostHeader post={mockPost} />)
224
+
225
+ const header = container.querySelector('[data-cy="post-header"]')
226
+ expect(header).toBeInTheDocument()
227
+ })
228
+
229
+ it('should have data-cy on featured image container', () => {
230
+ const { container } = render(<PostHeader post={mockPost} />)
231
+
232
+ const imageContainer = container.querySelector('[data-cy="post-featured-image-display"]')
233
+ expect(imageContainer).toBeInTheDocument()
234
+ })
235
+
236
+ it('should have data-cy on categories display', () => {
237
+ const { container } = render(<PostHeader post={mockPost} />)
238
+
239
+ const categoriesDisplay = container.querySelector('[data-cy="post-categories-display"]')
240
+ expect(categoriesDisplay).toBeInTheDocument()
241
+ })
242
+
243
+ it('should have data-cy on post title', () => {
244
+ const { container } = render(<PostHeader post={mockPost} />)
245
+
246
+ const title = container.querySelector('[data-cy="post-title"]')
247
+ expect(title).toBeInTheDocument()
248
+ expect(title?.textContent).toBe('Test Post Title')
249
+ })
250
+
251
+ it('should have data-cy on post excerpt', () => {
252
+ const { container } = render(<PostHeader post={mockPost} />)
253
+
254
+ const excerpt = container.querySelector('[data-cy="post-excerpt"]')
255
+ expect(excerpt).toBeInTheDocument()
256
+ expect(excerpt?.textContent).toBe('This is a test excerpt for the post')
257
+ })
258
+ })
259
+
260
+ describe('Edge Cases', () => {
261
+ it('should handle empty title', () => {
262
+ const postWithEmptyTitle = {
263
+ ...mockPost,
264
+ title: ''
265
+ }
266
+
267
+ const { container } = render(<PostHeader post={postWithEmptyTitle} />)
268
+
269
+ // Component should still render without crashing
270
+ const header = container.querySelector('[data-cy="post-header"]')
271
+ expect(header).toBeInTheDocument()
272
+ })
273
+
274
+ it('should handle very long title', () => {
275
+ const longTitle = 'A'.repeat(500)
276
+ const postWithLongTitle = {
277
+ ...mockPost,
278
+ title: longTitle
279
+ }
280
+
281
+ render(<PostHeader post={postWithLongTitle} />)
282
+
283
+ const title = screen.getByText(longTitle)
284
+ expect(title).toBeInTheDocument()
285
+ })
286
+
287
+ it('should handle very long excerpt', () => {
288
+ const longExcerpt = 'B'.repeat(1000)
289
+ const postWithLongExcerpt = {
290
+ ...mockPost,
291
+ excerpt: longExcerpt
292
+ }
293
+
294
+ render(<PostHeader post={postWithLongExcerpt} />)
295
+
296
+ const excerpt = screen.getByText(longExcerpt)
297
+ expect(excerpt).toBeInTheDocument()
298
+ })
299
+
300
+ it('should throw error for invalid date', () => {
301
+ const postWithInvalidDate = {
302
+ ...mockPost,
303
+ createdAt: 'invalid-date'
304
+ }
305
+
306
+ // Component throws RangeError when date is invalid (toISOString fails)
307
+ expect(() => render(<PostHeader post={postWithInvalidDate} />)).toThrow(RangeError)
308
+ })
309
+
310
+ it('should handle categories with very long names', () => {
311
+ const postWithLongCategoryName = {
312
+ ...mockPost,
313
+ categories: [
314
+ { id: 'cat-1', name: 'C'.repeat(100), color: '#FF0000' }
315
+ ]
316
+ }
317
+
318
+ render(<PostHeader post={postWithLongCategoryName} />)
319
+
320
+ const categoryBadge = screen.getByText('C'.repeat(100))
321
+ expect(categoryBadge).toBeInTheDocument()
322
+ })
323
+
324
+ it('should handle special characters in title', () => {
325
+ const postWithSpecialChars = {
326
+ ...mockPost,
327
+ title: 'Test <Title> & "Quotes" & Special ©haracters'
328
+ }
329
+
330
+ render(<PostHeader post={postWithSpecialChars} />)
331
+
332
+ const title = screen.getByText(/Test <Title> & "Quotes" & Special ©haracters/)
333
+ expect(title).toBeInTheDocument()
334
+ })
335
+
336
+ it('should handle missing category id', () => {
337
+ const postWithInvalidCategory = {
338
+ ...mockPost,
339
+ categories: [
340
+ { id: '', name: 'Invalid Category', color: '#FF0000' }
341
+ ]
342
+ }
343
+
344
+ render(<PostHeader post={postWithInvalidCategory} />)
345
+
346
+ // Should still render category name
347
+ expect(screen.getByText('Invalid Category')).toBeInTheDocument()
348
+ })
349
+ })
350
+
351
+ describe('Component Structure', () => {
352
+ it('should render in correct order: image -> categories -> title -> excerpt -> date', () => {
353
+ const { container } = render(<PostHeader post={mockPost} />)
354
+
355
+ const elements = container.querySelectorAll('[data-cy]')
356
+ const order = Array.from(elements).map(el => el.getAttribute('data-cy'))
357
+
358
+ // Expected order based on component structure
359
+ expect(order).toContain('post-featured-image-display')
360
+ expect(order).toContain('post-categories-display')
361
+ expect(order).toContain('post-title')
362
+ expect(order).toContain('post-excerpt')
363
+
364
+ // Featured image should come before title
365
+ const imageIndex = order.indexOf('post-featured-image-display')
366
+ const titleIndex = order.indexOf('post-title')
367
+ expect(imageIndex).toBeLessThan(titleIndex)
368
+ })
369
+
370
+ it('should render separator (hr) at the bottom', () => {
371
+ const { container } = render(<PostHeader post={mockPost} />)
372
+
373
+ const separator = container.querySelector('hr')
374
+ expect(separator).toBeInTheDocument()
375
+ })
376
+ })
377
+ })