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

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,170 @@
1
+ /**
2
+ * Jest Setup for Default Theme
3
+ *
4
+ * This file provides complete Jest setup for npm mode.
5
+ * It includes all necessary polyfills and mocks.
6
+ */
7
+
8
+ import '@testing-library/jest-dom'
9
+ import { TextEncoder, TextDecoder } from 'util'
10
+
11
+ // Polyfill TextEncoder/TextDecoder for jsdom
12
+ global.TextEncoder = TextEncoder
13
+ global.TextDecoder = TextDecoder as typeof global.TextDecoder
14
+
15
+ // Polyfill Web APIs for Next.js server components testing
16
+ class MockHeaders {
17
+ private _headers: Map<string, string> = new Map()
18
+
19
+ constructor(init?: HeadersInit) {
20
+ if (init) {
21
+ if (init instanceof MockHeaders) {
22
+ init._headers.forEach((value, key) => this._headers.set(key, value))
23
+ } else if (Array.isArray(init)) {
24
+ init.forEach(([key, value]) => this._headers.set(key.toLowerCase(), value))
25
+ } else {
26
+ Object.entries(init).forEach(([key, value]) => this._headers.set(key.toLowerCase(), value))
27
+ }
28
+ }
29
+ }
30
+
31
+ get(name: string) { return this._headers.get(name.toLowerCase()) || null }
32
+ set(name: string, value: string) { this._headers.set(name.toLowerCase(), value) }
33
+ has(name: string) { return this._headers.has(name.toLowerCase()) }
34
+ delete(name: string) { this._headers.delete(name.toLowerCase()) }
35
+ forEach(cb: (value: string, key: string) => void) { this._headers.forEach(cb) }
36
+ entries() { return this._headers.entries() }
37
+ keys() { return this._headers.keys() }
38
+ values() { return this._headers.values() }
39
+ }
40
+
41
+ class MockRequest {
42
+ url: string
43
+ method: string
44
+ headers: MockHeaders
45
+ private _body: any
46
+
47
+ constructor(input: string | URL, init?: RequestInit) {
48
+ this.url = typeof input === 'string' ? input : input.toString()
49
+ this.method = init?.method || 'GET'
50
+ this.headers = new MockHeaders(init?.headers as HeadersInit)
51
+ this._body = init?.body
52
+ }
53
+
54
+ async json() {
55
+ if (!this._body) return {}
56
+ return typeof this._body === 'string' ? JSON.parse(this._body) : this._body
57
+ }
58
+
59
+ async text() {
60
+ return this._body?.toString() || ''
61
+ }
62
+
63
+ clone() {
64
+ return new MockRequest(this.url, {
65
+ method: this.method,
66
+ headers: Object.fromEntries(this.headers.entries()),
67
+ body: this._body,
68
+ })
69
+ }
70
+ }
71
+
72
+ class MockResponse {
73
+ body: any
74
+ status: number
75
+ statusText: string
76
+ headers: MockHeaders
77
+ ok: boolean
78
+
79
+ constructor(body?: any, init?: ResponseInit) {
80
+ this.body = body
81
+ this.status = init?.status || 200
82
+ this.statusText = init?.statusText || 'OK'
83
+ this.headers = new MockHeaders(init?.headers as HeadersInit)
84
+ this.ok = this.status >= 200 && this.status < 300
85
+ }
86
+
87
+ async json() {
88
+ if (!this.body) return {}
89
+ return typeof this.body === 'string' ? JSON.parse(this.body) : this.body
90
+ }
91
+
92
+ async text() {
93
+ return this.body?.toString() || ''
94
+ }
95
+
96
+ clone() {
97
+ return new MockResponse(this.body, {
98
+ status: this.status,
99
+ statusText: this.statusText,
100
+ headers: Object.fromEntries(this.headers.entries()),
101
+ })
102
+ }
103
+
104
+ static json(data: any, init?: ResponseInit) {
105
+ return new MockResponse(JSON.stringify(data), {
106
+ ...init,
107
+ headers: { 'Content-Type': 'application/json', ...(init?.headers || {}) as object },
108
+ })
109
+ }
110
+ }
111
+
112
+ // Assign to global scope
113
+ global.Headers = MockHeaders as any
114
+ global.Request = MockRequest as any
115
+ global.Response = MockResponse as any
116
+
117
+ // Mock fetch
118
+ global.fetch = jest.fn().mockResolvedValue(new MockResponse('{}', { status: 200 }))
119
+
120
+ // Mock Web Crypto API
121
+ const crypto = require('crypto')
122
+ Object.defineProperty(global, 'crypto', {
123
+ value: {
124
+ getRandomValues: (arr: Uint8Array) => crypto.randomFillSync(arr),
125
+ randomUUID: () => crypto.randomUUID(),
126
+ subtle: {
127
+ digest: async (algorithm: string, data: BufferSource) => {
128
+ const hash = crypto.createHash(algorithm.toLowerCase().replace('-', ''))
129
+ hash.update(Buffer.from(data as ArrayBuffer))
130
+ return hash.digest().buffer
131
+ },
132
+ encrypt: jest.fn(),
133
+ decrypt: jest.fn(),
134
+ sign: jest.fn(),
135
+ verify: jest.fn(),
136
+ generateKey: jest.fn(),
137
+ importKey: jest.fn(),
138
+ exportKey: jest.fn(),
139
+ },
140
+ },
141
+ })
142
+
143
+ // Mock matchMedia for component tests with media queries
144
+ Object.defineProperty(window, 'matchMedia', {
145
+ writable: true,
146
+ value: jest.fn().mockImplementation((query) => ({
147
+ matches: false,
148
+ media: query,
149
+ onchange: null,
150
+ addListener: jest.fn(),
151
+ removeListener: jest.fn(),
152
+ addEventListener: jest.fn(),
153
+ removeEventListener: jest.fn(),
154
+ dispatchEvent: jest.fn(),
155
+ })),
156
+ })
157
+
158
+ // Mock ResizeObserver
159
+ global.ResizeObserver = jest.fn().mockImplementation(() => ({
160
+ observe: jest.fn(),
161
+ unobserve: jest.fn(),
162
+ disconnect: jest.fn(),
163
+ }))
164
+
165
+ // Mock IntersectionObserver
166
+ global.IntersectionObserver = jest.fn().mockImplementation(() => ({
167
+ observe: jest.fn(),
168
+ unobserve: jest.fn(),
169
+ disconnect: jest.fn(),
170
+ }))
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "../../../../apps/dev/tsconfig.json",
3
+ "compilerOptions": {
4
+ "jsx": "react-jsx"
5
+ }
6
+ }
@@ -0,0 +1,429 @@
1
+ /**
2
+ * Unit Tests - Post Categories Validation Schemas
3
+ *
4
+ * Tests Zod validation for post categories API:
5
+ * - createCategorySchema: Name, slug, parentId, icon, color
6
+ * - Slug auto-generation logic
7
+ * - Edge cases: Special characters, empty strings, hierarchical validation
8
+ *
9
+ * Focus on validation logic WITHOUT database calls.
10
+ */
11
+
12
+ import { z } from 'zod'
13
+
14
+ // Copy schemas from API route to test in isolation
15
+ const createCategorySchema = z.object({
16
+ name: z.string().min(1).max(255),
17
+ slug: z.string().min(2).max(100).regex(/^[a-z0-9-]+$/, 'Slug must be lowercase alphanumeric with dashes').optional(),
18
+ description: z.string().optional(),
19
+ icon: z.string().optional(),
20
+ color: z.string().optional(),
21
+ parentId: z.string().optional(),
22
+ order: z.number().int().default(0),
23
+ isDefault: z.boolean().default(false)
24
+ })
25
+
26
+ // Copy helper function from API route
27
+ function generateSlug(name: string): string {
28
+ return name
29
+ .toLowerCase()
30
+ .trim()
31
+ .replace(/[^a-z0-9\s-]/g, '') // Remove special chars
32
+ .replace(/\s+/g, '-') // Replace spaces with dashes
33
+ .replace(/-+/g, '-') // Replace multiple dashes with single
34
+ .replace(/^-|-$/g, '') // Remove leading/trailing dashes
35
+ }
36
+
37
+ describe('Post Categories Validation', () => {
38
+ describe('createCategorySchema - Valid Data', () => {
39
+ it('should accept minimal valid category data', () => {
40
+ const validData = {
41
+ name: 'Technology'
42
+ }
43
+
44
+ const result = createCategorySchema.safeParse(validData)
45
+
46
+ expect(result.success).toBe(true)
47
+ if (result.success) {
48
+ expect(result.data.name).toBe('Technology')
49
+ expect(result.data.order).toBe(0) // Default value
50
+ expect(result.data.isDefault).toBe(false) // Default value
51
+ }
52
+ })
53
+
54
+ it('should accept complete category data with all fields', () => {
55
+ const completeData = {
56
+ name: 'Web Development',
57
+ slug: 'web-development',
58
+ description: 'Posts about web development',
59
+ icon: 'code',
60
+ color: '#3B82F6',
61
+ parentId: 'parent-cat-id',
62
+ order: 5,
63
+ isDefault: true
64
+ }
65
+
66
+ const result = createCategorySchema.safeParse(completeData)
67
+
68
+ expect(result.success).toBe(true)
69
+ if (result.success) {
70
+ expect(result.data.name).toBe('Web Development')
71
+ expect(result.data.slug).toBe('web-development')
72
+ expect(result.data.description).toBe('Posts about web development')
73
+ expect(result.data.icon).toBe('code')
74
+ expect(result.data.color).toBe('#3B82F6')
75
+ expect(result.data.parentId).toBe('parent-cat-id')
76
+ expect(result.data.order).toBe(5)
77
+ expect(result.data.isDefault).toBe(true)
78
+ }
79
+ })
80
+
81
+ it('should accept category without slug (for auto-generation)', () => {
82
+ const data = {
83
+ name: 'Test Category'
84
+ }
85
+
86
+ const result = createCategorySchema.safeParse(data)
87
+
88
+ expect(result.success).toBe(true)
89
+ if (result.success) {
90
+ expect(result.data.slug).toBeUndefined()
91
+ }
92
+ })
93
+ })
94
+
95
+ describe('createCategorySchema - Name Validation', () => {
96
+ it('should reject empty name', () => {
97
+ const data = {
98
+ name: ''
99
+ }
100
+
101
+ const result = createCategorySchema.safeParse(data)
102
+
103
+ expect(result.success).toBe(false)
104
+ if (!result.success) {
105
+ expect(result.error.issues[0].path).toContain('name')
106
+ }
107
+ })
108
+
109
+ it('should reject name longer than 255 characters', () => {
110
+ const data = {
111
+ name: 'a'.repeat(256)
112
+ }
113
+
114
+ const result = createCategorySchema.safeParse(data)
115
+
116
+ expect(result.success).toBe(false)
117
+ })
118
+
119
+ it('should accept name with exactly 255 characters', () => {
120
+ const data = {
121
+ name: 'a'.repeat(255)
122
+ }
123
+
124
+ const result = createCategorySchema.safeParse(data)
125
+
126
+ expect(result.success).toBe(true)
127
+ })
128
+
129
+ it('should accept name with special characters', () => {
130
+ const validNames = [
131
+ 'Technology & Science',
132
+ 'Front-end Development',
133
+ 'Tips/Tricks',
134
+ 'Best Practices (2024)',
135
+ ]
136
+
137
+ validNames.forEach(name => {
138
+ const result = createCategorySchema.safeParse({ name })
139
+ expect(result.success).toBe(true)
140
+ })
141
+ })
142
+ })
143
+
144
+ describe('createCategorySchema - Slug Validation', () => {
145
+ it('should reject slug with uppercase letters', () => {
146
+ const data = {
147
+ name: 'Test',
148
+ slug: 'Test-Category'
149
+ }
150
+
151
+ const result = createCategorySchema.safeParse(data)
152
+
153
+ expect(result.success).toBe(false)
154
+ if (!result.success) {
155
+ expect(result.error.issues[0].message).toContain('lowercase')
156
+ }
157
+ })
158
+
159
+ it('should reject slug with special characters', () => {
160
+ const invalidSlugs = [
161
+ 'test_category',
162
+ 'test.category',
163
+ 'test category',
164
+ 'test@category',
165
+ ]
166
+
167
+ invalidSlugs.forEach(slug => {
168
+ const result = createCategorySchema.safeParse({ name: 'Test', slug })
169
+ expect(result.success).toBe(false)
170
+ })
171
+ })
172
+
173
+ it('should reject slug shorter than 2 characters', () => {
174
+ const data = {
175
+ name: 'Test',
176
+ slug: 'a'
177
+ }
178
+
179
+ const result = createCategorySchema.safeParse(data)
180
+
181
+ expect(result.success).toBe(false)
182
+ })
183
+
184
+ it('should reject slug longer than 100 characters', () => {
185
+ const data = {
186
+ name: 'Test',
187
+ slug: 'a'.repeat(101)
188
+ }
189
+
190
+ const result = createCategorySchema.safeParse(data)
191
+
192
+ expect(result.success).toBe(false)
193
+ })
194
+
195
+ it('should accept valid slugs', () => {
196
+ const validSlugs = [
197
+ 'technology',
198
+ 'web-development',
199
+ 'tips-tricks-2024',
200
+ 'category-123',
201
+ ]
202
+
203
+ validSlugs.forEach(slug => {
204
+ const result = createCategorySchema.safeParse({ name: 'Test', slug })
205
+ expect(result.success).toBe(true)
206
+ })
207
+ })
208
+ })
209
+
210
+ describe('createCategorySchema - Optional Fields', () => {
211
+ it('should accept missing description', () => {
212
+ const data = {
213
+ name: 'Test'
214
+ }
215
+
216
+ const result = createCategorySchema.safeParse(data)
217
+
218
+ expect(result.success).toBe(true)
219
+ if (result.success) {
220
+ expect(result.data.description).toBeUndefined()
221
+ }
222
+ })
223
+
224
+ it('should accept missing icon', () => {
225
+ const data = {
226
+ name: 'Test'
227
+ }
228
+
229
+ const result = createCategorySchema.safeParse(data)
230
+
231
+ expect(result.success).toBe(true)
232
+ if (result.success) {
233
+ expect(result.data.icon).toBeUndefined()
234
+ }
235
+ })
236
+
237
+ it('should accept missing color', () => {
238
+ const data = {
239
+ name: 'Test'
240
+ }
241
+
242
+ const result = createCategorySchema.safeParse(data)
243
+
244
+ expect(result.success).toBe(true)
245
+ if (result.success) {
246
+ expect(result.data.color).toBeUndefined()
247
+ }
248
+ })
249
+
250
+ it('should accept missing parentId', () => {
251
+ const data = {
252
+ name: 'Test'
253
+ }
254
+
255
+ const result = createCategorySchema.safeParse(data)
256
+
257
+ expect(result.success).toBe(true)
258
+ if (result.success) {
259
+ expect(result.data.parentId).toBeUndefined()
260
+ }
261
+ })
262
+ })
263
+
264
+ describe('createCategorySchema - Order Validation', () => {
265
+ it('should default order to 0', () => {
266
+ const data = {
267
+ name: 'Test'
268
+ }
269
+
270
+ const result = createCategorySchema.safeParse(data)
271
+
272
+ expect(result.success).toBe(true)
273
+ if (result.success) {
274
+ expect(result.data.order).toBe(0)
275
+ }
276
+ })
277
+
278
+ it('should accept positive order values', () => {
279
+ const data = {
280
+ name: 'Test',
281
+ order: 5
282
+ }
283
+
284
+ const result = createCategorySchema.safeParse(data)
285
+
286
+ expect(result.success).toBe(true)
287
+ if (result.success) {
288
+ expect(result.data.order).toBe(5)
289
+ }
290
+ })
291
+
292
+ it('should accept negative order values', () => {
293
+ const data = {
294
+ name: 'Test',
295
+ order: -1
296
+ }
297
+
298
+ const result = createCategorySchema.safeParse(data)
299
+
300
+ expect(result.success).toBe(true)
301
+ })
302
+
303
+ it('should reject non-integer order values', () => {
304
+ const data = {
305
+ name: 'Test',
306
+ order: 5.5
307
+ }
308
+
309
+ const result = createCategorySchema.safeParse(data)
310
+
311
+ expect(result.success).toBe(false)
312
+ })
313
+ })
314
+ })
315
+
316
+ describe('Slug Generation Logic', () => {
317
+ describe('generateSlug - Basic Functionality', () => {
318
+ it('should convert name to lowercase', () => {
319
+ expect(generateSlug('Technology')).toBe('technology')
320
+ expect(generateSlug('WEB DEVELOPMENT')).toBe('web-development')
321
+ expect(generateSlug('MixedCase')).toBe('mixedcase')
322
+ })
323
+
324
+ it('should replace spaces with dashes', () => {
325
+ expect(generateSlug('Web Development')).toBe('web-development')
326
+ expect(generateSlug('Tips and Tricks')).toBe('tips-and-tricks')
327
+ expect(generateSlug('Multiple Spaces')).toBe('multiple-spaces')
328
+ })
329
+
330
+ it('should trim leading and trailing whitespace', () => {
331
+ expect(generateSlug(' Technology ')).toBe('technology')
332
+ expect(generateSlug('\tWeb Development\n')).toBe('web-development')
333
+ })
334
+
335
+ it('should remove leading and trailing dashes', () => {
336
+ expect(generateSlug('-Technology-')).toBe('technology')
337
+ expect(generateSlug('--Web Development--')).toBe('web-development')
338
+ })
339
+ })
340
+
341
+ describe('generateSlug - Special Characters', () => {
342
+ it('should remove special characters', () => {
343
+ expect(generateSlug('Technology & Science')).toBe('technology-science')
344
+ expect(generateSlug('Front-end Development')).toBe('front-end-development')
345
+ expect(generateSlug('Tips/Tricks')).toBe('tipstricks')
346
+ expect(generateSlug('Category @ 2024')).toBe('category-2024')
347
+ expect(generateSlug('Test!@#$%Category')).toBe('testcategory')
348
+ })
349
+
350
+ it('should preserve numbers', () => {
351
+ expect(generateSlug('2024 Trends')).toBe('2024-trends')
352
+ expect(generateSlug('Top 10 Tips')).toBe('top-10-tips')
353
+ expect(generateSlug('Web 3.0')).toBe('web-30')
354
+ })
355
+
356
+ it('should handle parentheses', () => {
357
+ expect(generateSlug('Best Practices (2024)')).toBe('best-practices-2024')
358
+ expect(generateSlug('(Old) Category')).toBe('old-category')
359
+ })
360
+ })
361
+
362
+ describe('generateSlug - Multiple Dashes', () => {
363
+ it('should replace multiple dashes with single dash', () => {
364
+ expect(generateSlug('Web--Development')).toBe('web-development')
365
+ expect(generateSlug('Test---Category')).toBe('test-category')
366
+ expect(generateSlug('Multiple Spaces')).toBe('multiple-spaces')
367
+ })
368
+
369
+ it('should handle combination of special chars and spaces', () => {
370
+ expect(generateSlug('Web & Mobile Development')).toBe('web-mobile-development')
371
+ expect(generateSlug('Tips / Tricks 2024')).toBe('tips-tricks-2024')
372
+ })
373
+ })
374
+
375
+ describe('generateSlug - Edge Cases', () => {
376
+ it('should handle empty string', () => {
377
+ expect(generateSlug('')).toBe('')
378
+ })
379
+
380
+ it('should handle only special characters', () => {
381
+ expect(generateSlug('!@#$%')).toBe('')
382
+ expect(generateSlug('---')).toBe('')
383
+ })
384
+
385
+ it('should handle only spaces', () => {
386
+ expect(generateSlug(' ')).toBe('')
387
+ })
388
+
389
+ it('should handle unicode characters', () => {
390
+ // Non-ASCII characters should be removed
391
+ expect(generateSlug('Café')).toBe('caf')
392
+ expect(generateSlug('Niño Category')).toBe('nio-category')
393
+ })
394
+
395
+ it('should handle very long names', () => {
396
+ const longName = 'This is a very long category name that should be converted to a slug properly'
397
+ const slug = generateSlug(longName)
398
+
399
+ expect(slug).toBe('this-is-a-very-long-category-name-that-should-be-converted-to-a-slug-properly')
400
+ expect(slug.startsWith('-')).toBe(false)
401
+ expect(slug.endsWith('-')).toBe(false)
402
+ })
403
+
404
+ it('should handle already valid slugs', () => {
405
+ expect(generateSlug('web-development')).toBe('web-development')
406
+ expect(generateSlug('technology')).toBe('technology')
407
+ expect(generateSlug('tips-123')).toBe('tips-123')
408
+ })
409
+ })
410
+
411
+ describe('generateSlug - Real World Examples', () => {
412
+ it('should generate correct slugs for common category names', () => {
413
+ const examples = [
414
+ { name: 'Technology', expected: 'technology' },
415
+ { name: 'Web Development', expected: 'web-development' },
416
+ { name: 'Tips & Tricks', expected: 'tips-tricks' },
417
+ { name: 'News (2024)', expected: 'news-2024' },
418
+ { name: 'Front-end Development', expected: 'front-end-development' },
419
+ { name: 'AI / Machine Learning', expected: 'ai-machine-learning' },
420
+ { name: 'Product Reviews', expected: 'product-reviews' },
421
+ { name: 'How-to Guides', expected: 'how-to-guides' },
422
+ ]
423
+
424
+ examples.forEach(({ name, expected }) => {
425
+ expect(generateSlug(name)).toBe(expected)
426
+ })
427
+ })
428
+ })
429
+ })