@nextsparkjs/theme-default 0.1.0-beta.1 → 0.1.0-beta.101

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 (330) hide show
  1. package/LICENSE +21 -0
  2. package/api/ai/chat/stream/route.ts +4 -1
  3. package/api/ai/orchestrator/route.ts +10 -3
  4. package/api/ai/single-agent/route.ts +10 -3
  5. package/api/ai/usage/route.ts +4 -1
  6. package/blocks/benefits/component.tsx +4 -4
  7. package/blocks/cta-section/component.tsx +4 -4
  8. package/blocks/faq-accordion/component.tsx +2 -2
  9. package/blocks/features-grid/component.tsx +5 -5
  10. package/blocks/hero/component.tsx +2 -2
  11. package/blocks/hero/fields.ts +1 -1
  12. package/blocks/hero-with-form/component.tsx +7 -7
  13. package/blocks/hero-with-form/fields.ts +1 -1
  14. package/blocks/jumbotron/component.tsx +7 -7
  15. package/blocks/jumbotron/fields.ts +1 -1
  16. package/blocks/logo-cloud/component.tsx +6 -6
  17. package/blocks/logo-cloud/fields.ts +1 -1
  18. package/blocks/post-content/component.tsx +2 -2
  19. package/blocks/pricing-table/component.tsx +5 -5
  20. package/blocks/split-content/component.tsx +5 -5
  21. package/blocks/split-content/fields.ts +1 -1
  22. package/blocks/stats-counter/component.tsx +9 -9
  23. package/blocks/testimonials/component.tsx +4 -4
  24. package/blocks/testimonials/fields.ts +1 -1
  25. package/blocks/text-content/component.tsx +12 -10
  26. package/blocks/timeline/component.tsx +12 -12
  27. package/blocks/video-hero/component.tsx +7 -7
  28. package/blocks/video-hero/fields.ts +1 -1
  29. package/components/ai-chat/ChatPanel.tsx +7 -7
  30. package/components/ai-chat/Message.tsx +2 -2
  31. package/components/ai-chat/MessageInput.tsx +3 -3
  32. package/components/ai-chat/MessageList.tsx +3 -3
  33. package/components/ai-chat/TypingIndicator.tsx +2 -2
  34. package/config/app.config.ts +75 -62
  35. package/config/dashboard.config.ts +14 -0
  36. package/config/features.config.ts +10 -0
  37. package/config/permissions.config.ts +26 -1
  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/entities/customers/api/docs.md +107 -0
  48. package/entities/customers/api/presets.ts +80 -0
  49. package/entities/pages/api/docs.md +114 -0
  50. package/entities/pages/api/presets.ts +72 -0
  51. package/entities/posts/api/docs.md +120 -0
  52. package/entities/posts/api/presets.ts +74 -0
  53. package/entities/tasks/api/docs.md +126 -0
  54. package/entities/tasks/api/presets.ts +84 -0
  55. package/lib/selectors.ts +7 -4
  56. package/messages/de/admin.json +45 -0
  57. package/messages/en/admin.json +56 -0
  58. package/messages/en/navigation.json +2 -1
  59. package/messages/es/admin.json +56 -0
  60. package/messages/es/navigation.json +2 -1
  61. package/messages/fr/admin.json +45 -0
  62. package/messages/it/admin.json +45 -0
  63. package/messages/pt/admin.json +45 -0
  64. package/migrations/090_demo_users_teams.sql +11 -11
  65. package/migrations/091_greek_teams_billing.sql +15 -15
  66. package/migrations/093_pages_sample_data.sql +7 -7
  67. package/migrations/098_patterns_sample_data.sql +234 -0
  68. package/package.json +8 -3
  69. package/styles/globals.css +42 -0
  70. package/templates/(public)/blog/[slug]/page.tsx +1 -1
  71. package/templates/(public)/page.tsx +1 -1
  72. package/tests/cypress/e2e/_utils/devtools/access.bdd.md +262 -0
  73. package/tests/cypress/e2e/_utils/devtools/access.cy.ts +171 -0
  74. package/tests/cypress/e2e/_utils/devtools/navigation.bdd.md +261 -0
  75. package/tests/cypress/e2e/_utils/devtools/navigation.cy.ts +157 -0
  76. package/tests/cypress/e2e/_utils/devtools/pages.bdd.md +303 -0
  77. package/tests/cypress/e2e/_utils/devtools/pages.cy.ts +184 -0
  78. package/tests/cypress/e2e/_utils/docs/README.md +215 -0
  79. package/tests/cypress/e2e/_utils/selectors/auth.bdd.md +354 -0
  80. package/tests/cypress/e2e/_utils/selectors/auth.cy.ts +310 -0
  81. package/tests/cypress/e2e/_utils/selectors/billing.bdd.md +276 -0
  82. package/tests/cypress/e2e/_utils/selectors/billing.cy.ts +182 -0
  83. package/tests/cypress/e2e/_utils/selectors/block-editor.bdd.md +615 -0
  84. package/tests/cypress/e2e/_utils/selectors/block-editor.cy.ts +783 -0
  85. package/tests/cypress/e2e/_utils/selectors/dashboard-container.cy.ts +52 -0
  86. package/tests/cypress/e2e/_utils/selectors/dashboard-mobile.bdd.md +205 -0
  87. package/tests/cypress/e2e/_utils/selectors/dashboard-mobile.cy.ts +137 -0
  88. package/tests/cypress/e2e/_utils/selectors/dashboard-navigation.bdd.md +147 -0
  89. package/tests/cypress/e2e/_utils/selectors/dashboard-navigation.cy.ts +114 -0
  90. package/tests/cypress/e2e/_utils/selectors/dashboard-sidebar.bdd.md +76 -0
  91. package/tests/cypress/e2e/_utils/selectors/dashboard-sidebar.cy.ts +68 -0
  92. package/tests/cypress/e2e/_utils/selectors/dashboard-topnav.bdd.md +326 -0
  93. package/tests/cypress/e2e/_utils/selectors/dashboard-topnav.cy.ts +177 -0
  94. package/tests/cypress/e2e/_utils/selectors/devtools.bdd.md +306 -0
  95. package/tests/cypress/e2e/_utils/selectors/devtools.cy.ts +273 -0
  96. package/tests/cypress/e2e/_utils/selectors/global-search.bdd.md +115 -0
  97. package/tests/cypress/e2e/_utils/selectors/global-search.cy.ts +93 -0
  98. package/tests/cypress/e2e/_utils/selectors/patterns.bdd.md +388 -0
  99. package/tests/cypress/e2e/_utils/selectors/patterns.cy.ts +559 -0
  100. package/tests/cypress/e2e/_utils/selectors/public.cy.ts +112 -0
  101. package/tests/cypress/e2e/_utils/selectors/settings-api-keys.bdd.md +266 -0
  102. package/tests/cypress/e2e/_utils/selectors/settings-api-keys.cy.ts +233 -0
  103. package/tests/cypress/e2e/_utils/selectors/settings-billing.bdd.md +78 -0
  104. package/tests/cypress/e2e/_utils/selectors/settings-billing.cy.ts +108 -0
  105. package/tests/cypress/e2e/_utils/selectors/settings-layout.bdd.md +129 -0
  106. package/tests/cypress/e2e/_utils/selectors/settings-layout.cy.ts +115 -0
  107. package/tests/cypress/e2e/_utils/selectors/settings-password.bdd.md +82 -0
  108. package/tests/cypress/e2e/_utils/selectors/settings-password.cy.ts +74 -0
  109. package/tests/cypress/e2e/_utils/selectors/settings-profile.bdd.md +77 -0
  110. package/tests/cypress/e2e/_utils/selectors/settings-profile.cy.ts +79 -0
  111. package/tests/cypress/e2e/_utils/selectors/settings-teams.bdd.md +130 -0
  112. package/tests/cypress/e2e/_utils/selectors/settings-teams.cy.ts +86 -0
  113. package/tests/cypress/e2e/_utils/selectors/superadmin.bdd.md +261 -0
  114. package/tests/cypress/e2e/_utils/selectors/superadmin.cy.ts +193 -0
  115. package/tests/cypress/e2e/_utils/selectors/tasks.bdd.md +593 -0
  116. package/tests/cypress/e2e/_utils/selectors/tasks.cy.ts +864 -0
  117. package/tests/cypress/e2e/_utils/selectors/taxonomies.cy.ts +126 -0
  118. package/tests/cypress/e2e/_utils/selectors/teams.bdd.md +278 -0
  119. package/tests/cypress/e2e/_utils/selectors/teams.cy.ts +195 -0
  120. package/tests/cypress/e2e/_utils/superadmin/all-teams.bdd.md +261 -0
  121. package/tests/cypress/e2e/_utils/superadmin/all-teams.cy.ts +177 -0
  122. package/tests/cypress/e2e/_utils/superadmin/all-users.bdd.md +406 -0
  123. package/tests/cypress/e2e/_utils/superadmin/all-users.cy.ts +294 -0
  124. package/tests/cypress/e2e/_utils/superadmin/dashboard.bdd.md +235 -0
  125. package/tests/cypress/e2e/_utils/superadmin/dashboard.cy.ts +149 -0
  126. package/tests/cypress/e2e/_utils/superadmin/subscriptions-overview.bdd.md +290 -0
  127. package/tests/cypress/e2e/_utils/superadmin/subscriptions-overview.cy.ts +194 -0
  128. package/tests/cypress/e2e/ai/ai-usage.cy.ts +209 -0
  129. package/tests/cypress/e2e/ai/chat-api.cy.ts +119 -0
  130. package/tests/cypress/e2e/ai/guardrails.cy.ts +332 -0
  131. package/tests/cypress/e2e/api/_core/billing/BillingAPIController.js +319 -0
  132. package/tests/cypress/e2e/api/_core/billing/check-action.cy.ts +326 -0
  133. package/tests/cypress/e2e/api/_core/billing/checkout.cy.ts +358 -0
  134. package/tests/cypress/e2e/api/_core/billing/lifecycle.cy.ts +423 -0
  135. package/tests/cypress/e2e/api/_core/billing/plans/README.md +345 -0
  136. package/tests/cypress/e2e/api/_core/billing/plans/business.cy.ts +412 -0
  137. package/tests/cypress/e2e/api/_core/billing/plans/downgrade.cy.ts +510 -0
  138. package/tests/cypress/e2e/api/_core/billing/plans/fixtures/billing-plans.json +163 -0
  139. package/tests/cypress/e2e/api/_core/billing/plans/free.cy.ts +500 -0
  140. package/tests/cypress/e2e/api/_core/billing/plans/pro.cy.ts +497 -0
  141. package/tests/cypress/e2e/api/_core/billing/plans/starter.cy.ts +342 -0
  142. package/tests/cypress/e2e/api/_core/billing/portal.cy.ts +313 -0
  143. package/tests/cypress/e2e/api/_core/devtools/registries.bdd.md +300 -0
  144. package/tests/cypress/e2e/api/_core/devtools/registries.cy.ts +368 -0
  145. package/tests/cypress/e2e/api/_core/scheduled-actions/cron-endpoint.bdd.md +375 -0
  146. package/tests/cypress/e2e/api/_core/scheduled-actions/cron-endpoint.cy.ts +346 -0
  147. package/tests/cypress/e2e/api/_core/scheduled-actions/devtools-endpoint.bdd.md +451 -0
  148. package/tests/cypress/e2e/api/_core/scheduled-actions/devtools-endpoint.cy.ts +447 -0
  149. package/tests/cypress/e2e/api/_core/scheduled-actions/scheduling.bdd.md +649 -0
  150. package/tests/cypress/e2e/api/_core/scheduled-actions/scheduling.cy.ts +333 -0
  151. package/tests/cypress/e2e/api/_core/security/security-headers.cy.ts +601 -0
  152. package/tests/cypress/e2e/api/_core/settings/api-keys.crud.cy.ts +923 -0
  153. package/tests/cypress/e2e/api/_core/teams/teams-security.cy.ts +415 -0
  154. package/tests/cypress/e2e/api/_core/users/users-crud.cy.ts +469 -0
  155. package/tests/cypress/e2e/api/_core/users/users-metas.cy.ts +913 -0
  156. package/tests/cypress/e2e/api/_core/users/users-security.cy.ts +375 -0
  157. package/tests/cypress/e2e/api/entities/customers/customers-crud.cy.ts +648 -0
  158. package/tests/cypress/e2e/api/entities/customers/customers-metas.cy.ts +839 -0
  159. package/tests/cypress/e2e/api/entities/media/media-crud.cy.ts +600 -0
  160. package/tests/cypress/e2e/api/entities/media/media-role-permissions.cy.ts +617 -0
  161. package/tests/cypress/e2e/api/entities/media/media-team-isolation.cy.ts +464 -0
  162. package/tests/cypress/e2e/api/entities/pages/blocks-scope.cy.ts +396 -0
  163. package/tests/cypress/e2e/api/entities/pages/pages-crud.cy.ts +425 -0
  164. package/tests/cypress/e2e/api/entities/pages/pages-status.cy.ts +335 -0
  165. package/tests/cypress/e2e/api/entities/posts/post-categories-crud.cy.ts +610 -0
  166. package/tests/cypress/e2e/api/entities/posts/posts-crud.cy.ts +709 -0
  167. package/tests/cypress/e2e/api/entities/posts/posts-status.cy.ts +396 -0
  168. package/tests/cypress/e2e/api/entities/tasks/tasks-crud.cy.ts +602 -0
  169. package/tests/cypress/e2e/api/entities/tasks/tasks-metas.cy.ts +878 -0
  170. package/tests/cypress/e2e/patterns/patterns-in-pages.cy.ts +367 -0
  171. package/tests/cypress/e2e/uat/_core/auth/app-roles/developer-login.bdd.md +231 -0
  172. package/tests/cypress/e2e/uat/_core/auth/app-roles/developer-login.cy.ts +144 -0
  173. package/tests/cypress/e2e/uat/_core/auth/app-roles/superadmin-login.bdd.md +118 -0
  174. package/tests/cypress/e2e/uat/_core/auth/app-roles/superadmin-login.cy.ts +84 -0
  175. package/tests/cypress/e2e/uat/_core/auth/custom-roles/editor-login.bdd.md +288 -0
  176. package/tests/cypress/e2e/uat/_core/auth/custom-roles/editor-login.cy.ts +188 -0
  177. package/tests/cypress/e2e/uat/_core/auth/login-logout.bdd.md +160 -0
  178. package/tests/cypress/e2e/uat/_core/auth/login-logout.cy.ts +116 -0
  179. package/tests/cypress/e2e/uat/_core/auth/password-reset.bdd.md +289 -0
  180. package/tests/cypress/e2e/uat/_core/auth/password-reset.cy.ts +200 -0
  181. package/tests/cypress/e2e/uat/_core/auth/registration-control-invitation.cy.ts +176 -0
  182. package/tests/cypress/e2e/uat/_core/auth/registration-control-open.cy.ts +131 -0
  183. package/tests/cypress/e2e/uat/_core/auth/registration-control.cy.ts +140 -0
  184. package/tests/cypress/e2e/uat/_core/auth/team-roles/admin-login.bdd.md +225 -0
  185. package/tests/cypress/e2e/uat/_core/auth/team-roles/admin-login.cy.ts +148 -0
  186. package/tests/cypress/e2e/uat/_core/auth/team-roles/member-login.bdd.md +251 -0
  187. package/tests/cypress/e2e/uat/_core/auth/team-roles/member-login.cy.ts +163 -0
  188. package/tests/cypress/e2e/uat/_core/auth/team-roles/owner-login.bdd.md +231 -0
  189. package/tests/cypress/e2e/uat/_core/auth/team-roles/owner-login.cy.ts +141 -0
  190. package/tests/cypress/e2e/uat/_core/billing/extended.bdd.md +273 -0
  191. package/tests/cypress/e2e/uat/_core/billing/extended.cy.ts +209 -0
  192. package/tests/cypress/e2e/uat/_core/billing/feature-gates.bdd.md +407 -0
  193. package/tests/cypress/e2e/uat/_core/billing/feature-gates.cy.ts +307 -0
  194. package/tests/cypress/e2e/uat/_core/billing/page.bdd.md +329 -0
  195. package/tests/cypress/e2e/uat/_core/billing/page.cy.ts +250 -0
  196. package/tests/cypress/e2e/uat/_core/billing/status.bdd.md +190 -0
  197. package/tests/cypress/e2e/uat/_core/billing/status.cy.ts +145 -0
  198. package/tests/cypress/e2e/uat/_core/billing/team-switch.bdd.md +156 -0
  199. package/tests/cypress/e2e/uat/_core/billing/team-switch.cy.ts +122 -0
  200. package/tests/cypress/e2e/uat/_core/billing/usage.bdd.md +218 -0
  201. package/tests/cypress/e2e/uat/_core/billing/usage.cy.ts +176 -0
  202. package/tests/cypress/e2e/uat/_core/blocks/hero.bdd.md +124 -0
  203. package/tests/cypress/e2e/uat/_core/blocks/hero.cy.ts +56 -0
  204. package/tests/cypress/e2e/uat/_core/devtools/api-tester.cy.ts +390 -0
  205. package/tests/cypress/e2e/uat/_core/performance/suspense-loading.cy.ts +134 -0
  206. package/tests/cypress/e2e/uat/_core/scheduled-actions/devtools-ui.bdd.md +736 -0
  207. package/tests/cypress/e2e/uat/_core/scheduled-actions/devtools-ui.cy.ts +740 -0
  208. package/tests/cypress/e2e/uat/_core/teams/inline-edit.cy.ts +278 -0
  209. package/tests/cypress/e2e/uat/_core/teams/roles-matrix.bdd.md +553 -0
  210. package/tests/cypress/e2e/uat/_core/teams/roles-matrix.cy.ts +185 -0
  211. package/tests/cypress/e2e/uat/_core/teams/switcher.bdd.md +1151 -0
  212. package/tests/cypress/e2e/uat/_core/teams/switcher.cy.ts +497 -0
  213. package/tests/cypress/e2e/uat/_core/teams/team-switcher.md +198 -0
  214. package/tests/cypress/e2e/uat/entities/customers/member.bdd.md +275 -0
  215. package/tests/cypress/e2e/uat/entities/customers/member.cy.ts +122 -0
  216. package/tests/cypress/e2e/uat/entities/customers/owner.bdd.md +243 -0
  217. package/tests/cypress/e2e/uat/entities/customers/owner.cy.ts +165 -0
  218. package/tests/cypress/e2e/uat/entities/pages/block-crud.bdd.md +476 -0
  219. package/tests/cypress/e2e/uat/entities/pages/block-crud.cy.ts +486 -0
  220. package/tests/cypress/e2e/uat/entities/pages/block-editor.bdd.md +460 -0
  221. package/tests/cypress/e2e/uat/entities/pages/block-editor.cy.ts +301 -0
  222. package/tests/cypress/e2e/uat/entities/pages/list.bdd.md +432 -0
  223. package/tests/cypress/e2e/uat/entities/pages/list.cy.ts +273 -0
  224. package/tests/cypress/e2e/uat/entities/pages/public-rendering.bdd.md +696 -0
  225. package/tests/cypress/e2e/uat/entities/pages/public-rendering.cy.ts +340 -0
  226. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.bdd.md +161 -0
  227. package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.cy.ts +104 -0
  228. package/tests/cypress/e2e/uat/entities/posts/categories.bdd.md +375 -0
  229. package/tests/cypress/e2e/uat/entities/posts/categories.cy.ts +241 -0
  230. package/tests/cypress/e2e/uat/entities/posts/editor.bdd.md +429 -0
  231. package/tests/cypress/e2e/uat/entities/posts/editor.cy.ts +257 -0
  232. package/tests/cypress/e2e/uat/entities/posts/list.bdd.md +340 -0
  233. package/tests/cypress/e2e/uat/entities/posts/list.cy.ts +177 -0
  234. package/tests/cypress/e2e/uat/entities/posts/public.bdd.md +614 -0
  235. package/tests/cypress/e2e/uat/entities/posts/public.cy.ts +249 -0
  236. package/tests/cypress/e2e/uat/entities/tasks/member.bdd.md +222 -0
  237. package/tests/cypress/e2e/uat/entities/tasks/member.cy.ts +165 -0
  238. package/tests/cypress/e2e/uat/entities/tasks/owner.bdd.md +419 -0
  239. package/tests/cypress/e2e/uat/entities/tasks/owner.cy.ts +191 -0
  240. package/tests/cypress/e2e/uat/features/roles/editor-role.bdd.md +552 -0
  241. package/tests/cypress/e2e/uat/features/roles/editor-role.cy.ts +210 -0
  242. package/tests/cypress/e2e/uat/features/roles/member-restrictions.bdd.md +450 -0
  243. package/tests/cypress/e2e/uat/features/roles/member-restrictions.cy.ts +189 -0
  244. package/tests/cypress/e2e/uat/features/roles/owner-full-crud.bdd.md +530 -0
  245. package/tests/cypress/e2e/uat/features/roles/owner-full-crud.cy.ts +247 -0
  246. package/tests/cypress/fixtures/blocks.json +218 -0
  247. package/tests/cypress/fixtures/entities.json +87 -0
  248. package/tests/cypress/fixtures/page-builder.json +21 -0
  249. package/tests/cypress/src/components/CategoriesPOM.ts +382 -0
  250. package/tests/cypress/src/components/CustomersPOM.ts +439 -0
  251. package/tests/cypress/src/components/DevKeyringPOM.ts +160 -0
  252. package/tests/cypress/src/components/EntityForm.ts +375 -0
  253. package/tests/cypress/src/components/EntityList.ts +389 -0
  254. package/tests/cypress/src/components/PageBuilderPOM.ts +710 -0
  255. package/tests/cypress/src/components/PostEditorPOM.ts +370 -0
  256. package/tests/cypress/src/components/PostsListPOM.ts +223 -0
  257. package/tests/cypress/src/components/PublicPagePOM.ts +447 -0
  258. package/tests/cypress/src/components/PublicPostPOM.ts +146 -0
  259. package/tests/cypress/src/components/TasksPOM.ts +272 -0
  260. package/tests/cypress/src/components/TeamSwitcherPOM.ts +450 -0
  261. package/tests/cypress/src/components/index.ts +21 -0
  262. package/tests/cypress/src/controllers/ApiKeysAPIController.js +178 -0
  263. package/tests/cypress/src/controllers/BaseAPIController.js +317 -0
  264. package/tests/cypress/src/controllers/CustomerAPIController.js +251 -0
  265. package/tests/cypress/src/controllers/MediaAPIController.js +231 -0
  266. package/tests/cypress/src/controllers/PagesAPIController.js +226 -0
  267. package/tests/cypress/src/controllers/PostsAPIController.js +250 -0
  268. package/tests/cypress/src/controllers/TaskAPIController.js +240 -0
  269. package/tests/cypress/src/controllers/UsersAPIController.js +242 -0
  270. package/tests/cypress/src/controllers/index.js +25 -0
  271. package/tests/cypress/src/core/AuthPOM.ts +450 -0
  272. package/tests/cypress/src/core/BasePOM.ts +33 -0
  273. package/tests/cypress/src/core/BlockEditorBasePOM.ts +874 -0
  274. package/tests/cypress/src/core/DashboardEntityPOM.ts +41 -0
  275. package/tests/cypress/src/core/index.ts +14 -0
  276. package/tests/cypress/src/entities/CustomersPOM.ts +172 -0
  277. package/tests/cypress/src/entities/PagesPOM.ts +137 -0
  278. package/tests/cypress/src/entities/PatternsPOM.ts +329 -0
  279. package/tests/cypress/src/entities/PostsPOM.ts +137 -0
  280. package/tests/cypress/src/entities/TasksPOM.ts +246 -0
  281. package/tests/cypress/src/entities/index.ts +16 -0
  282. package/tests/cypress/src/features/BillingPOM.ts +385 -0
  283. package/tests/cypress/src/features/DashboardPOM.ts +271 -0
  284. package/tests/cypress/src/features/DevtoolsPOM.ts +750 -0
  285. package/tests/cypress/src/features/PageBuilderPOM.ts +283 -0
  286. package/tests/cypress/src/features/PostEditorPOM.ts +313 -0
  287. package/tests/cypress/src/features/ScheduledActionsPOM.ts +463 -0
  288. package/tests/cypress/src/features/SettingsPOM.ts +707 -0
  289. package/tests/cypress/src/features/SuperadminPOM.ts +851 -0
  290. package/tests/cypress/src/features/SuperadminTeamRolesPOM.ts +285 -0
  291. package/tests/cypress/src/features/index.ts +28 -0
  292. package/tests/cypress/src/helpers/ApiInterceptor.ts +20 -0
  293. package/tests/cypress/src/index.ts +101 -0
  294. package/tests/cypress/src/pages/dashboard/Dashboard.js +677 -0
  295. package/tests/cypress/src/pages/dashboard/DashboardPage.js +43 -0
  296. package/tests/cypress/src/pages/dashboard/DashboardStats.js +546 -0
  297. package/tests/cypress/src/pages/dashboard/index.js +6 -0
  298. package/tests/cypress/src/pages/index.js +5 -0
  299. package/tests/cypress/src/pages/public/FeaturesPage.js +28 -0
  300. package/tests/cypress/src/pages/public/LandingPage.js +69 -0
  301. package/tests/cypress/src/pages/public/PricingPage.js +33 -0
  302. package/tests/cypress/src/pages/public/index.js +6 -0
  303. package/tests/cypress/src/selectors.ts +46 -0
  304. package/tests/cypress/src/session-helpers.ts +518 -0
  305. package/tests/cypress/support/doc-commands.ts +260 -0
  306. package/tests/cypress/support/e2e.ts +90 -0
  307. package/tests/cypress.config.ts +178 -0
  308. package/tests/jest/__mocks__/@nextsparkjs/core/components/ui/badge.js +16 -0
  309. package/tests/jest/__mocks__/@nextsparkjs/core/lib/db.js +11 -0
  310. package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +160 -0
  311. package/tests/jest/__mocks__/@nextsparkjs/registries/theme-registry.ts +68 -0
  312. package/tests/jest/__mocks__/jose.js +22 -0
  313. package/tests/jest/__mocks__/next/image.js +15 -0
  314. package/tests/jest/__mocks__/next-server.js +56 -0
  315. package/tests/jest/components/post-header.test.tsx +377 -0
  316. package/tests/jest/jest.config.cjs +154 -0
  317. package/tests/jest/langchain/COVERAGE.md +372 -0
  318. package/tests/jest/langchain/guardrails.test.ts +465 -0
  319. package/tests/jest/langchain/streaming.test.ts +370 -0
  320. package/tests/jest/langchain/token-tracker.test.ts +455 -0
  321. package/tests/jest/langchain/tracer-callbacks.test.ts +881 -0
  322. package/tests/jest/langchain/tracer.test.ts +823 -0
  323. package/tests/jest/services/tasks.service.test.ts +707 -0
  324. package/tests/jest/setup.ts +170 -0
  325. package/tests/jest/tsconfig.jest.json +6 -0
  326. package/tests/jest/validation/categories.test.ts +429 -0
  327. package/tests/jest/validation/posts.test.ts +546 -0
  328. package/tests/tsconfig.json +21 -0
  329. /package/docs/{02-features → public/02-features}/01-components.md +0 -0
  330. /package/docs/{02-features → public/02-features}/02-styling.md +0 -0
@@ -0,0 +1,864 @@
1
+ /**
2
+ * UI Selectors Validation: Tasks Entity
3
+ *
4
+ * This test validates that all entity selectors exist in the DOM.
5
+ * Organized by the 6 first-level keys in ENTITIES_SELECTORS:
6
+ *
7
+ * ═══════════════════════════════════════════════════════════════════════════════
8
+ * STRUCTURE (matches entities.selectors.ts)
9
+ * ═══════════════════════════════════════════════════════════════════════════════
10
+ *
11
+ * 1. PAGE - Page-level container
12
+ * 2. LIST - List view (search, filters, table, pagination, bulk, confirm)
13
+ * 3. HEADER - Entity detail header (view/edit/create modes)
14
+ * 4. DETAIL - Detail view container
15
+ * 5. FORM - Form container, fields, and actions
16
+ * 6. CHILD - Child entity management (not applicable for tasks)
17
+ *
18
+ * ═══════════════════════════════════════════════════════════════════════════════
19
+ *
20
+ * Test IDs:
21
+ * - SEL_TASK_PAGE_001: Page Container
22
+ * - SEL_TASK_LIST_001: Search Selectors
23
+ * - SEL_TASK_LIST_002: Table Structure Selectors
24
+ * - SEL_TASK_LIST_003: Row Dynamic Selectors
25
+ * - SEL_TASK_LIST_004: Pagination Selectors
26
+ * - SEL_TASK_LIST_005: Filter Selectors
27
+ * - SEL_TASK_LIST_006: Bulk Action Selectors
28
+ * - SEL_TASK_LIST_007: Confirm Dialog Selectors
29
+ * - SEL_TASK_HEADER_001: Header Selectors (all modes)
30
+ * - SEL_TASK_DETAIL_001: Detail Container
31
+ * - SEL_TASK_FORM_001: Form Selectors
32
+ *
33
+ * POM: TasksPOM extends DashboardEntityPOM
34
+ * Selectors: ENTITIES_SELECTORS (6 first-level keys)
35
+ */
36
+
37
+ import { TasksPOM } from '../../../src/entities/TasksPOM'
38
+ import { loginAsDefaultOwner } from '../../../src/session-helpers'
39
+
40
+ describe('Tasks Entity Selectors Validation', { tags: ['@ui-selectors', '@entities', '@tasks'] }, () => {
41
+ const tasks = TasksPOM.create()
42
+
43
+ beforeEach(() => {
44
+ // Login as Carlos Mendoza (owner) - has proper team context via setupTeamContext()
45
+ loginAsDefaultOwner()
46
+ })
47
+
48
+ // ═══════════════════════════════════════════════════════════════════════════════
49
+ // 1. PAGE - Page-level container
50
+ // ═══════════════════════════════════════════════════════════════════════════════
51
+ describe('SEL_TASK_PAGE_001: Page Container', { tags: '@SEL_TASK_PAGE_001' }, () => {
52
+ beforeEach(() => {
53
+ tasks.visitList()
54
+ tasks.waitForList()
55
+ })
56
+
57
+ /**
58
+ * Selector: entities.page.container
59
+ * POM: tasks.selectors.page
60
+ * data-cy: tasks-page
61
+ */
62
+ it.skip('should find page container (not implemented in EntityList)', () => {
63
+ // NOTE: page.container selector not currently implemented in EntityList component
64
+ // The page container would wrap the entire entity list view
65
+ cy.get(tasks.selectors.page).should('exist')
66
+ })
67
+ })
68
+
69
+ // ═══════════════════════════════════════════════════════════════════════════════
70
+ // 2. LIST - List view selectors
71
+ // ═══════════════════════════════════════════════════════════════════════════════
72
+ describe('LIST Selectors', { tags: '@entities-list' }, () => {
73
+ // ---------------------------------------------------------------------------
74
+ // 2.1 SEARCH - Search input and controls
75
+ // ---------------------------------------------------------------------------
76
+ describe('SEL_TASK_LIST_001: Search Selectors', { tags: '@SEL_TASK_LIST_001' }, () => {
77
+ beforeEach(() => {
78
+ tasks.visitList()
79
+ tasks.waitForList()
80
+ })
81
+
82
+ /**
83
+ * Selector: entities.list.search.input
84
+ * POM: tasks.selectors.search
85
+ * data-cy: tasks-search-input
86
+ */
87
+ it('should find search input', () => {
88
+ cy.get(tasks.selectors.search).should('exist')
89
+ })
90
+
91
+ /**
92
+ * Selector: entities.list.search.container
93
+ * POM: tasks.selectors.searchContainer
94
+ * data-cy: tasks-search
95
+ */
96
+ it('should find search container', () => {
97
+ cy.get(tasks.selectors.searchContainer).should('exist')
98
+ })
99
+
100
+ /**
101
+ * Selector: entities.list.search.clear
102
+ * POM: tasks.selectors.searchClear
103
+ * data-cy: tasks-search-clear
104
+ */
105
+ it.skip('should find search clear button (not implemented)', () => {
106
+ // NOTE: searchClear selector not currently implemented in EntityTable
107
+ cy.get(tasks.selectors.searchClear).should('exist')
108
+ })
109
+ })
110
+
111
+ // ---------------------------------------------------------------------------
112
+ // 2.2 TABLE - Table structure selectors
113
+ // ---------------------------------------------------------------------------
114
+ describe('SEL_TASK_LIST_002: Table Structure Selectors', { tags: '@SEL_TASK_LIST_002' }, () => {
115
+ beforeEach(() => {
116
+ tasks.visitList()
117
+ tasks.waitForList()
118
+ })
119
+
120
+ /**
121
+ * Selector: entities.list.table.container
122
+ * POM: tasks.selectors.tableContainer
123
+ * data-cy: tasks-table-container
124
+ */
125
+ it('should find table container', () => {
126
+ cy.get(tasks.selectors.tableContainer).should('exist')
127
+ })
128
+
129
+ /**
130
+ * Selector: entities.list.table.element
131
+ * POM: tasks.selectors.table
132
+ * data-cy: tasks-table
133
+ * NOTE: Table element only renders when there's data (not empty state)
134
+ */
135
+ it('should find table element (requires data)', () => {
136
+ // Skip if no rows exist (empty state)
137
+ cy.get('body').then(($body) => {
138
+ if ($body.find(tasks.selectors.rowGeneric).length > 0) {
139
+ cy.get(tasks.selectors.table).should('exist')
140
+ } else {
141
+ cy.log('⚠️ Skipping: No data - empty state showing')
142
+ }
143
+ })
144
+ })
145
+
146
+ /**
147
+ * Selector: entities.list.addButton
148
+ * POM: tasks.selectors.addButton
149
+ * data-cy: tasks-add
150
+ */
151
+ it('should find add button', () => {
152
+ cy.get(tasks.selectors.addButton).should('exist')
153
+ })
154
+
155
+ /**
156
+ * Selector: entities.list.table.selectAll
157
+ * POM: tasks.selectors.selectAll
158
+ * data-cy: tasks-select-all
159
+ * NOTE: SelectAll checkbox only renders when there's data and selectable=true
160
+ */
161
+ it('should find select all checkbox (requires data)', () => {
162
+ // Skip if no rows exist (empty state)
163
+ cy.get('body').then(($body) => {
164
+ if ($body.find(tasks.selectors.rowGeneric).length > 0) {
165
+ cy.get(tasks.selectors.selectAll).should('exist')
166
+ } else {
167
+ cy.log('⚠️ Skipping: No data - empty state showing')
168
+ }
169
+ })
170
+ })
171
+
172
+ /**
173
+ * Selector: entities.list.selectionCount
174
+ * POM: tasks.selectors.selectionCount
175
+ * data-cy: tasks-selection-count
176
+ * NOTE: Only visible when items are selected
177
+ */
178
+ it('should find selection count after selecting items (requires data)', () => {
179
+ cy.get('body').then(($body) => {
180
+ if ($body.find(tasks.selectors.rowGeneric).length === 0) {
181
+ cy.log('⚠️ Skipping: No data - empty state showing')
182
+ return
183
+ }
184
+
185
+ // Select the first row to make selectionCount appear
186
+ cy.get(tasks.selectors.rowGeneric)
187
+ .first()
188
+ .invoke('attr', 'data-cy')
189
+ .then((dataCy) => {
190
+ const id = dataCy?.replace('tasks-row-', '') || ''
191
+ cy.get(tasks.selectors.rowSelect(id)).click()
192
+
193
+ // Now selectionCount should be visible
194
+ cy.get(tasks.selectors.selectionCount).should('exist')
195
+
196
+ // Clear selection
197
+ cy.get(tasks.selectors.rowSelect(id)).click()
198
+ })
199
+ })
200
+ })
201
+ })
202
+
203
+ // ---------------------------------------------------------------------------
204
+ // 2.3 ROW - Dynamic row selectors
205
+ // ---------------------------------------------------------------------------
206
+ describe('SEL_TASK_LIST_003: Row Dynamic Selectors', { tags: '@SEL_TASK_LIST_003' }, () => {
207
+ beforeEach(() => {
208
+ tasks.visitList()
209
+ tasks.waitForList()
210
+ })
211
+
212
+ /**
213
+ * Selector: entities.list.table.row.element (dynamic)
214
+ * POM: tasks.selectors.row(id)
215
+ * data-cy: tasks-row-{id}
216
+ * NOTE: Rows only render when there's data
217
+ */
218
+ it('should find row elements with rowGeneric pattern (requires data)', () => {
219
+ cy.get('body').then(($body) => {
220
+ if ($body.find(tasks.selectors.rowGeneric).length > 0) {
221
+ cy.get(tasks.selectors.rowGeneric).should('have.length.at.least', 1)
222
+ } else {
223
+ cy.log('⚠️ Skipping: No data - empty state showing')
224
+ }
225
+ })
226
+ })
227
+
228
+ /**
229
+ * Tests dynamic selectors: row, rowSelect, rowMenu
230
+ * NOTE: Requires data to exist
231
+ */
232
+ it('should find row with dynamic ID selectors (requires data)', () => {
233
+ cy.get('body').then(($body) => {
234
+ if ($body.find(tasks.selectors.rowGeneric).length === 0) {
235
+ cy.log('⚠️ Skipping: No data - empty state showing')
236
+ return
237
+ }
238
+
239
+ cy.get(tasks.selectors.rowGeneric)
240
+ .first()
241
+ .invoke('attr', 'data-cy')
242
+ .then((dataCy) => {
243
+ const id = dataCy?.replace('tasks-row-', '') || ''
244
+ expect(id).to.not.be.empty
245
+
246
+ // Selector: entities.list.table.row.element
247
+ cy.get(tasks.selectors.row(id)).should('exist')
248
+
249
+ // Selector: entities.list.table.row.checkbox
250
+ cy.get(tasks.selectors.rowSelect(id)).should('exist')
251
+
252
+ // Selector: entities.list.table.row.menu
253
+ cy.get(tasks.selectors.rowMenu(id)).should('exist')
254
+ })
255
+ })
256
+ })
257
+
258
+ /**
259
+ * Selector: entities.list.table.row.action (dynamic)
260
+ * POM: tasks.selectors.rowAction(action, id)
261
+ * data-cy: tasks-action-{action}-{id}
262
+ * NOTE: Requires data to exist
263
+ */
264
+ it('should find row action selectors in dropdown (requires data)', () => {
265
+ cy.get('body').then(($body) => {
266
+ if ($body.find(tasks.selectors.rowGeneric).length === 0) {
267
+ cy.log('⚠️ Skipping: No data - empty state showing')
268
+ return
269
+ }
270
+
271
+ cy.get(tasks.selectors.rowGeneric)
272
+ .first()
273
+ .invoke('attr', 'data-cy')
274
+ .then((dataCy) => {
275
+ const id = dataCy?.replace('tasks-row-', '') || ''
276
+
277
+ // Open row menu
278
+ cy.get(tasks.selectors.rowMenu(id)).click()
279
+
280
+ // Check action selectors exist
281
+ cy.get(tasks.selectors.rowAction('view', id)).should('exist')
282
+ cy.get(tasks.selectors.rowAction('edit', id)).should('exist')
283
+ cy.get(tasks.selectors.rowAction('delete', id)).should('exist')
284
+
285
+ // Close menu by pressing Escape
286
+ cy.get('body').type('{esc}')
287
+ })
288
+ })
289
+ })
290
+ })
291
+
292
+ // ---------------------------------------------------------------------------
293
+ // 2.4 PAGINATION - Pagination controls
294
+ // NOTE: Pagination only renders when there's data (not in empty state)
295
+ // ---------------------------------------------------------------------------
296
+ describe('SEL_TASK_LIST_004: Pagination Selectors', { tags: '@SEL_TASK_LIST_004' }, () => {
297
+ beforeEach(() => {
298
+ tasks.visitList()
299
+ tasks.waitForList()
300
+ })
301
+
302
+ /**
303
+ * Selector: entities.list.pagination.container
304
+ * POM: tasks.selectors.pagination
305
+ * data-cy: tasks-pagination
306
+ * NOTE: Requires data to exist
307
+ */
308
+ it('should find pagination container (requires data)', () => {
309
+ cy.get('body').then(($body) => {
310
+ if ($body.find(tasks.selectors.rowGeneric).length === 0) {
311
+ cy.log('⚠️ Skipping: No data - pagination not rendered in empty state')
312
+ return
313
+ }
314
+ cy.get(tasks.selectors.pagination).should('exist')
315
+ })
316
+ })
317
+
318
+ /**
319
+ * Selector: entities.list.pagination.info
320
+ * POM: tasks.selectors.pageInfo
321
+ * data-cy: tasks-page-info
322
+ * NOTE: Requires data to exist
323
+ */
324
+ it('should find page info (requires data)', () => {
325
+ cy.get('body').then(($body) => {
326
+ if ($body.find(tasks.selectors.rowGeneric).length === 0) {
327
+ cy.log('⚠️ Skipping: No data - pagination not rendered in empty state')
328
+ return
329
+ }
330
+ cy.get(tasks.selectors.pageInfo).should('exist')
331
+ })
332
+ })
333
+
334
+ /**
335
+ * Selector: entities.list.pagination.pageSize
336
+ * POM: tasks.selectors.pageSize
337
+ * data-cy: tasks-page-size
338
+ * NOTE: Requires data to exist
339
+ */
340
+ it('should find page size selector (requires data)', () => {
341
+ cy.get('body').then(($body) => {
342
+ if ($body.find(tasks.selectors.rowGeneric).length === 0) {
343
+ cy.log('⚠️ Skipping: No data - pagination not rendered in empty state')
344
+ return
345
+ }
346
+ cy.get(tasks.selectors.pageSize).should('exist')
347
+ })
348
+ })
349
+
350
+ /**
351
+ * Selectors: entities.list.pagination.{first,prev,next,last}
352
+ * NOTE: Requires data to exist
353
+ */
354
+ it('should find pagination navigation controls (requires data)', () => {
355
+ cy.get('body').then(($body) => {
356
+ if ($body.find(tasks.selectors.rowGeneric).length === 0) {
357
+ cy.log('⚠️ Skipping: No data - pagination not rendered in empty state')
358
+ return
359
+ }
360
+ cy.get(tasks.selectors.pageFirst).should('exist')
361
+ cy.get(tasks.selectors.pagePrev).should('exist')
362
+ cy.get(tasks.selectors.pageNext).should('exist')
363
+ cy.get(tasks.selectors.pageLast).should('exist')
364
+ })
365
+ })
366
+
367
+ /**
368
+ * Selector: entities.list.pagination.pageSizeOption (dynamic)
369
+ * POM: tasks.selectors.pageSizeOption(size)
370
+ * data-cy: tasks-page-size-{size}
371
+ * NOTE: Requires data to exist
372
+ */
373
+ it('should find page size options when dropdown opened (requires data)', () => {
374
+ cy.get('body').then(($body) => {
375
+ if ($body.find(tasks.selectors.rowGeneric).length === 0) {
376
+ cy.log('⚠️ Skipping: No data - pagination not rendered in empty state')
377
+ return
378
+ }
379
+ cy.get(tasks.selectors.pageSize).click()
380
+ cy.get(tasks.selectors.pageSizeOption('10')).should('exist')
381
+ cy.get(tasks.selectors.pageSizeOption('20')).should('exist')
382
+ cy.get('body').type('{esc}')
383
+ })
384
+ })
385
+ })
386
+
387
+ // ---------------------------------------------------------------------------
388
+ // 2.5 FILTERS - Filter controls
389
+ // ---------------------------------------------------------------------------
390
+ describe('SEL_TASK_LIST_005: Filter Selectors', { tags: '@SEL_TASK_LIST_005' }, () => {
391
+ beforeEach(() => {
392
+ tasks.visitList()
393
+ tasks.waitForList()
394
+ })
395
+
396
+ /**
397
+ * Selector: entities.list.filters.trigger
398
+ * POM: tasks.selectors.filterTrigger(field)
399
+ * data-cy: tasks-filter-{field}
400
+ */
401
+ it('should find status filter trigger', () => {
402
+ cy.get(tasks.selectors.filterTrigger('status')).should('exist')
403
+ })
404
+
405
+ it('should find priority filter trigger', () => {
406
+ cy.get(tasks.selectors.filterTrigger('priority')).should('exist')
407
+ })
408
+
409
+ /**
410
+ * Selector: entities.list.filters.content
411
+ * POM: tasks.selectors.filterContent(field)
412
+ * data-cy: tasks-filter-{field}-content
413
+ */
414
+ it('should find filter content when opened', () => {
415
+ tasks.openFilter('status')
416
+ cy.get(tasks.selectors.filterContent('status')).should('be.visible')
417
+ cy.get('body').type('{esc}')
418
+ })
419
+
420
+ /**
421
+ * Selector: entities.list.filters.option
422
+ * POM: tasks.selectors.filterOption(field, value)
423
+ * data-cy: tasks-filter-{field}-{value}
424
+ * NOTE: Status values are: todo, in-progress, review, done, blocked
425
+ */
426
+ it('should find filter options', () => {
427
+ tasks.openFilter('status')
428
+ cy.get(tasks.selectors.filterOption('status', 'todo')).should('exist')
429
+ cy.get('body').type('{esc}')
430
+ })
431
+
432
+ /**
433
+ * Selector: entities.list.filters.clearAll
434
+ * POM: tasks.selectors.filterClearAll(field)
435
+ * data-cy: tasks-filter-{field}-clear-all
436
+ * NOTE: Clear button only appears when >1 option is selected
437
+ */
438
+ it('should find filter clear button when multiple options selected', () => {
439
+ // Open status filter
440
+ tasks.openFilter('status')
441
+
442
+ // Select 2 options to make clear button appear
443
+ cy.get(tasks.selectors.filterOption('status', 'todo')).click()
444
+ cy.get(tasks.selectors.filterOption('status', 'in-progress')).click()
445
+
446
+ // Close dropdown
447
+ cy.get('body').type('{esc}')
448
+
449
+ // Clear button should now exist
450
+ cy.get(tasks.selectors.filterClearAll('status')).should('exist')
451
+
452
+ // Click to clear and verify it disappears
453
+ cy.get(tasks.selectors.filterClearAll('status')).click()
454
+ cy.get(tasks.selectors.filterClearAll('status')).should('not.exist')
455
+ })
456
+ })
457
+
458
+ // ---------------------------------------------------------------------------
459
+ // 2.6 BULK - Bulk action selectors
460
+ // ---------------------------------------------------------------------------
461
+ describe('SEL_TASK_LIST_006: Bulk Action Selectors', { tags: '@SEL_TASK_LIST_006' }, () => {
462
+ beforeEach(() => {
463
+ tasks.visitList()
464
+ tasks.waitForList()
465
+ })
466
+
467
+ /**
468
+ * Selectors: entities.list.bulk.{bar,count,delete,clear}
469
+ * Bulk bar appears after selecting items
470
+ * NOTE: Requires data to exist
471
+ */
472
+ it('should show bulk bar selectors after selecting rows (requires data)', () => {
473
+ cy.get('body').then(($body) => {
474
+ if ($body.find(tasks.selectors.rowGeneric).length === 0) {
475
+ cy.log('⚠️ Skipping: No data - empty state showing')
476
+ return
477
+ }
478
+
479
+ cy.get(tasks.selectors.rowGeneric)
480
+ .first()
481
+ .invoke('attr', 'data-cy')
482
+ .then((dataCy) => {
483
+ const id = dataCy?.replace('tasks-row-', '') || ''
484
+ cy.get(tasks.selectors.rowSelect(id)).click()
485
+
486
+ // Selector: entities.list.bulk.bar
487
+ cy.get(tasks.selectors.bulkBar).should('be.visible')
488
+
489
+ // Selector: entities.list.bulk.count
490
+ cy.get(tasks.selectors.bulkCount).should('exist')
491
+
492
+ // Selector: entities.list.bulk.deleteButton
493
+ cy.get(tasks.selectors.bulkDelete).should('exist')
494
+
495
+ // Selector: entities.list.bulk.clearButton
496
+ cy.get(tasks.selectors.bulkClear).should('exist')
497
+ })
498
+ })
499
+ })
500
+
501
+ /**
502
+ * Selector: entities.list.bulk.statusButton
503
+ * POM: tasks.selectors.bulkStatus
504
+ * NOTE: enableChangeStatus not enabled in EntityListWrapper for tasks
505
+ */
506
+ it.skip('should find bulk status button (enableChangeStatus not enabled)', () => {
507
+ cy.get(tasks.selectors.bulkStatus).should('exist')
508
+ })
509
+
510
+ /**
511
+ * Selectors: entities.list.bulk.deleteDialog, deleteCancel, deleteConfirm
512
+ * NOTE: Requires data to exist
513
+ */
514
+ it('should show bulk delete dialog selectors (requires data)', () => {
515
+ cy.get('body').then(($body) => {
516
+ if ($body.find(tasks.selectors.rowGeneric).length === 0) {
517
+ cy.log('⚠️ Skipping: No data - empty state showing')
518
+ return
519
+ }
520
+
521
+ cy.get(tasks.selectors.rowGeneric)
522
+ .first()
523
+ .invoke('attr', 'data-cy')
524
+ .then((dataCy) => {
525
+ const id = dataCy?.replace('tasks-row-', '') || ''
526
+ cy.get(tasks.selectors.rowSelect(id)).click()
527
+
528
+ // Open bulk delete dialog
529
+ cy.get(tasks.selectors.bulkDelete).click()
530
+
531
+ // Selector: entities.list.bulk.deleteDialog
532
+ cy.get(tasks.selectors.bulkDeleteDialog).should('be.visible')
533
+
534
+ // Selector: entities.list.bulk.deleteCancel
535
+ cy.get(tasks.selectors.bulkDeleteCancel).should('exist')
536
+
537
+ // Selector: entities.list.bulk.deleteConfirm
538
+ cy.get(tasks.selectors.bulkDeleteConfirm).should('exist')
539
+
540
+ // Cancel without deleting
541
+ cy.get(tasks.selectors.bulkDeleteCancel).click()
542
+ })
543
+ })
544
+ })
545
+ })
546
+
547
+ // ---------------------------------------------------------------------------
548
+ // 2.7 CONFIRM - Row action confirm dialogs
549
+ // ---------------------------------------------------------------------------
550
+ describe('SEL_TASK_LIST_007: Confirm Dialog Selectors', { tags: '@SEL_TASK_LIST_007' }, () => {
551
+ /**
552
+ * Selectors: entities.list.confirm.{dialog,cancel,action}
553
+ * Confirm dialog appears for row delete action
554
+ * NOTE: Requires data to exist
555
+ */
556
+ it('should show confirm dialog selectors for row delete (requires data)', () => {
557
+ tasks.visitList()
558
+ tasks.waitForList()
559
+
560
+ cy.get('body').then(($body) => {
561
+ if ($body.find(tasks.selectors.rowGeneric).length === 0) {
562
+ cy.log('⚠️ Skipping: No data - empty state showing')
563
+ return
564
+ }
565
+
566
+ cy.get(tasks.selectors.rowGeneric)
567
+ .first()
568
+ .invoke('attr', 'data-cy')
569
+ .then((dataCy) => {
570
+ const id = dataCy?.replace('tasks-row-', '') || ''
571
+
572
+ // Open row menu and click delete
573
+ cy.get(tasks.selectors.rowMenu(id)).click()
574
+ cy.get(tasks.selectors.rowAction('delete', id)).click()
575
+
576
+ // Selector: entities.list.confirm.dialog
577
+ cy.get(tasks.selectors.confirmDialog).should('be.visible')
578
+
579
+ // Selector: entities.list.confirm.cancel
580
+ cy.get(tasks.selectors.confirmCancel).should('exist')
581
+
582
+ // Selector: entities.list.confirm.action
583
+ cy.get(tasks.selectors.confirmAction).should('exist')
584
+
585
+ // Cancel without deleting
586
+ cy.get(tasks.selectors.confirmCancel).click()
587
+ })
588
+ })
589
+ })
590
+ })
591
+ })
592
+
593
+ // ═══════════════════════════════════════════════════════════════════════════════
594
+ // 3. HEADER - Entity detail header
595
+ // ═══════════════════════════════════════════════════════════════════════════════
596
+ describe('SEL_TASK_HEADER_001: Header Selectors', { tags: '@SEL_TASK_HEADER_001' }, () => {
597
+ /**
598
+ * Header selectors for CREATE mode
599
+ * Selectors: entities.header.container (mode: create), backButton
600
+ */
601
+ describe('Create Mode', () => {
602
+ beforeEach(() => {
603
+ tasks.visitCreate()
604
+ tasks.waitForForm()
605
+ })
606
+
607
+ /**
608
+ * Selector: entities.header.container (mode: create)
609
+ * POM: tasks.selectors.createHeader
610
+ * data-cy: tasks-create-header
611
+ */
612
+ it('should find create header', () => {
613
+ cy.get(tasks.selectors.createHeader).should('exist')
614
+ })
615
+
616
+ /**
617
+ * Selector: entities.header.backButton
618
+ * POM: tasks.selectors.backButton
619
+ * data-cy: tasks-back
620
+ */
621
+ it('should find back button', () => {
622
+ cy.get(tasks.selectors.backButton).should('exist')
623
+ })
624
+
625
+ /**
626
+ * Selector: entities.header.title
627
+ * POM: tasks.selectors.title
628
+ * data-cy: tasks-header-title
629
+ */
630
+ it('should find header title', () => {
631
+ cy.get(tasks.selectors.title).should('exist')
632
+ })
633
+ })
634
+
635
+ /**
636
+ * Header selectors for VIEW mode
637
+ * Selectors: entities.header.container (mode: view), backButton, editButton, deleteButton
638
+ * NOTE: Requires data to exist
639
+ */
640
+ describe('View Mode', () => {
641
+ it('should find view header selectors (requires data)', () => {
642
+ tasks.visitList()
643
+ tasks.waitForList()
644
+
645
+ cy.get('body').then(($body) => {
646
+ if ($body.find(tasks.selectors.rowGeneric).length === 0) {
647
+ cy.log('⚠️ Skipping: No data - empty state showing')
648
+ return
649
+ }
650
+
651
+ cy.get(tasks.selectors.rowGeneric)
652
+ .first()
653
+ .invoke('attr', 'data-cy')
654
+ .then((dataCy) => {
655
+ const id = dataCy?.replace('tasks-row-', '') || ''
656
+
657
+ tasks.visitDetail(id)
658
+ tasks.waitForDetail()
659
+
660
+ // Selector: entities.header.container (mode: view)
661
+ cy.get(tasks.selectors.viewHeader).should('exist')
662
+
663
+ // Selector: entities.header.backButton
664
+ cy.get(tasks.selectors.backButton).should('exist')
665
+
666
+ // Selector: entities.header.editButton
667
+ cy.get(tasks.selectors.editButton).should('exist')
668
+
669
+ // Selector: entities.header.deleteButton
670
+ cy.get(tasks.selectors.deleteButton).should('exist')
671
+ })
672
+ })
673
+ })
674
+
675
+ /**
676
+ * Selectors: entities.header.{deleteDialog,deleteCancel,deleteConfirm}
677
+ * NOTE: Requires data to exist
678
+ */
679
+ it('should find header delete dialog selectors (requires data)', () => {
680
+ tasks.visitList()
681
+ tasks.waitForList()
682
+
683
+ cy.get('body').then(($body) => {
684
+ if ($body.find(tasks.selectors.rowGeneric).length === 0) {
685
+ cy.log('⚠️ Skipping: No data - empty state showing')
686
+ return
687
+ }
688
+
689
+ cy.get(tasks.selectors.rowGeneric)
690
+ .first()
691
+ .invoke('attr', 'data-cy')
692
+ .then((dataCy) => {
693
+ const id = dataCy?.replace('tasks-row-', '') || ''
694
+
695
+ tasks.visitDetail(id)
696
+ tasks.waitForDetail()
697
+
698
+ // Click delete to open dialog
699
+ tasks.clickDelete()
700
+
701
+ // Selector: entities.header.deleteDialog
702
+ cy.get(tasks.selectors.deleteDialog).should('be.visible')
703
+
704
+ // Selector: entities.header.deleteConfirm
705
+ cy.get(tasks.selectors.deleteConfirm).should('exist')
706
+
707
+ // Selector: entities.header.deleteCancel
708
+ cy.get(tasks.selectors.deleteCancel).should('exist')
709
+
710
+ // Cancel without deleting
711
+ tasks.cancelDelete()
712
+ })
713
+ })
714
+ })
715
+ })
716
+
717
+ /**
718
+ * Header selectors for EDIT mode
719
+ * Selectors: entities.header.container (mode: edit), backButton
720
+ * NOTE: Requires data to exist
721
+ */
722
+ describe('Edit Mode', () => {
723
+ it('should find edit header selectors (requires data)', () => {
724
+ tasks.visitList()
725
+ tasks.waitForList()
726
+
727
+ cy.get('body').then(($body) => {
728
+ if ($body.find(tasks.selectors.rowGeneric).length === 0) {
729
+ cy.log('⚠️ Skipping: No data - empty state showing')
730
+ return
731
+ }
732
+
733
+ cy.get(tasks.selectors.rowGeneric)
734
+ .first()
735
+ .invoke('attr', 'data-cy')
736
+ .then((dataCy) => {
737
+ const id = dataCy?.replace('tasks-row-', '') || ''
738
+
739
+ tasks.visitEdit(id)
740
+ tasks.waitForForm()
741
+
742
+ // Selector: entities.header.container (mode: edit)
743
+ cy.get(tasks.selectors.editHeader).should('exist')
744
+
745
+ // Selector: entities.header.backButton
746
+ cy.get(tasks.selectors.backButton).should('exist')
747
+ })
748
+ })
749
+ })
750
+ })
751
+ })
752
+
753
+ // ═══════════════════════════════════════════════════════════════════════════════
754
+ // 4. DETAIL - Detail view container
755
+ // ═══════════════════════════════════════════════════════════════════════════════
756
+ describe('SEL_TASK_DETAIL_001: Detail Container', { tags: '@SEL_TASK_DETAIL_001' }, () => {
757
+ /**
758
+ * Selector: entities.detail.container
759
+ * POM: tasks.selectors.detail
760
+ * data-cy: tasks-detail
761
+ * NOTE: Requires data to exist
762
+ */
763
+ it('should find detail container (requires data)', () => {
764
+ tasks.visitList()
765
+ tasks.waitForList()
766
+
767
+ cy.get('body').then(($body) => {
768
+ if ($body.find(tasks.selectors.rowGeneric).length === 0) {
769
+ cy.log('⚠️ Skipping: No data - empty state showing')
770
+ return
771
+ }
772
+
773
+ cy.get(tasks.selectors.rowGeneric)
774
+ .first()
775
+ .invoke('attr', 'data-cy')
776
+ .then((dataCy) => {
777
+ const id = dataCy?.replace('tasks-row-', '') || ''
778
+
779
+ tasks.visitDetail(id)
780
+ tasks.waitForDetail()
781
+
782
+ cy.get(tasks.selectors.detail).should('exist')
783
+ })
784
+ })
785
+ })
786
+ })
787
+
788
+ // ═══════════════════════════════════════════════════════════════════════════════
789
+ // 5. FORM - Form selectors
790
+ // ═══════════════════════════════════════════════════════════════════════════════
791
+ describe('SEL_TASK_FORM_001: Form Selectors', { tags: '@SEL_TASK_FORM_001' }, () => {
792
+ beforeEach(() => {
793
+ tasks.visitCreate()
794
+ tasks.waitForForm()
795
+ })
796
+
797
+ /**
798
+ * Selector: entities.form.container
799
+ * POM: tasks.selectors.form
800
+ * data-cy: tasks-form
801
+ */
802
+ it('should find form container', () => {
803
+ cy.get(tasks.selectors.form).should('exist')
804
+ })
805
+
806
+ /**
807
+ * Selector: entities.form.submitButton
808
+ * POM: tasks.selectors.submitButton
809
+ * data-cy: tasks-form-submit
810
+ */
811
+ it('should find submit button', () => {
812
+ cy.get(tasks.selectors.submitButton).should('exist')
813
+ })
814
+
815
+ /**
816
+ * Selector: entities.form.field (dynamic)
817
+ * POM: tasks.selectors.field(name)
818
+ * data-cy: tasks-field-{name}
819
+ */
820
+ describe('Field Selectors', () => {
821
+ it('should find title field', () => {
822
+ cy.get(tasks.selectors.field('title')).should('exist')
823
+ })
824
+
825
+ it('should find description field', () => {
826
+ cy.get(tasks.selectors.field('description')).should('exist')
827
+ })
828
+
829
+ it('should find status field', () => {
830
+ cy.get(tasks.selectors.field('status')).should('exist')
831
+ })
832
+
833
+ it('should find priority field', () => {
834
+ cy.get(tasks.selectors.field('priority')).should('exist')
835
+ })
836
+ })
837
+ })
838
+
839
+ // ═══════════════════════════════════════════════════════════════════════════════
840
+ // 6. CHILD ENTITY - Child entity management (not applicable for tasks)
841
+ // ═══════════════════════════════════════════════════════════════════════════════
842
+ // NOTE: Tasks entity does not have child entities.
843
+ // If it did, tests would follow this pattern:
844
+ //
845
+ // describe('SEL_TASK_CHILD_001: Child Entity Selectors', { tags: '@SEL_TASK_CHILD_001' }, () => {
846
+ // /**
847
+ // * Selector: entities.childEntity.container
848
+ // * POM: tasks.selectors.childEntityContainer(childName)
849
+ // * data-cy: tasks-{childName}-section
850
+ // */
851
+ // it('should find child entity container', () => {
852
+ // cy.get(tasks.selectors.childEntityContainer('subtasks')).should('exist')
853
+ // })
854
+ //
855
+ // /**
856
+ // * Selector: entities.childEntity.addButton
857
+ // * POM: tasks.selectors.childEntityAddButton(childName)
858
+ // * data-cy: tasks-{childName}-add
859
+ // */
860
+ // it('should find child entity add button', () => {
861
+ // cy.get(tasks.selectors.childEntityAddButton('subtasks')).should('exist')
862
+ // })
863
+ // })
864
+ })