@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.
- package/api/ai/chat/stream/route.ts +4 -1
- package/api/ai/orchestrator/route.ts +10 -3
- package/api/ai/single-agent/route.ts +10 -3
- package/api/ai/usage/route.ts +4 -1
- package/blocks/benefits/component.tsx +4 -4
- package/blocks/cta-section/component.tsx +4 -4
- package/blocks/faq-accordion/component.tsx +2 -2
- package/blocks/features-grid/component.tsx +5 -5
- package/blocks/hero/component.tsx +2 -2
- package/blocks/hero/fields.ts +1 -1
- package/blocks/hero-with-form/component.tsx +7 -7
- package/blocks/hero-with-form/fields.ts +1 -1
- package/blocks/jumbotron/component.tsx +7 -7
- package/blocks/jumbotron/fields.ts +1 -1
- package/blocks/logo-cloud/component.tsx +6 -6
- package/blocks/logo-cloud/fields.ts +1 -1
- package/blocks/post-content/component.tsx +2 -2
- package/blocks/pricing-table/component.tsx +5 -5
- package/blocks/split-content/component.tsx +5 -5
- package/blocks/split-content/fields.ts +1 -1
- package/blocks/stats-counter/component.tsx +9 -9
- package/blocks/testimonials/component.tsx +4 -4
- package/blocks/testimonials/fields.ts +1 -1
- package/blocks/text-content/component.tsx +12 -10
- package/blocks/timeline/component.tsx +12 -12
- package/blocks/video-hero/component.tsx +7 -7
- package/blocks/video-hero/fields.ts +1 -1
- package/components/ai-chat/ChatPanel.tsx +7 -7
- package/components/ai-chat/Message.tsx +2 -2
- package/components/ai-chat/MessageInput.tsx +3 -3
- package/components/ai-chat/MessageList.tsx +3 -3
- package/components/ai-chat/TypingIndicator.tsx +2 -2
- package/config/app.config.ts +90 -62
- package/config/billing.config.ts +5 -10
- package/config/dashboard.config.ts +14 -0
- package/config/features.config.ts +10 -0
- package/config/permissions.config.ts +22 -23
- package/docs/{01-overview → public/01-overview}/01-introduction.md +5 -0
- package/docs/{01-overview → public/01-overview}/02-customization.md +5 -0
- package/docs/{02-features → public/02-features}/03-tasks-entity.md +5 -0
- package/docs/{03-ai → public/03-ai}/01-overview.md +5 -0
- package/docs/{03-ai → public/03-ai}/02-customization.md +5 -0
- package/docs/superadmin/01-setup/01-configuration.md +79 -0
- package/docs/superadmin/01-setup/02-deployment.md +82 -0
- package/docs/superadmin/02-management/01-users.md +83 -0
- package/docs/superadmin/03-integrations/01-langchain.md +139 -0
- package/emails/verify-email.ts +33 -0
- package/entities/customers/api/docs.md +107 -0
- package/entities/customers/api/presets.ts +80 -0
- package/entities/pages/api/docs.md +114 -0
- package/entities/pages/api/presets.ts +72 -0
- package/entities/posts/api/docs.md +120 -0
- package/entities/posts/api/presets.ts +74 -0
- package/entities/tasks/api/docs.md +126 -0
- package/entities/tasks/api/presets.ts +84 -0
- package/lib/selectors.ts +7 -4
- package/messages/de/admin.json +45 -0
- package/messages/en/admin.json +56 -0
- package/messages/en/navigation.json +2 -1
- package/messages/es/admin.json +56 -0
- package/messages/es/navigation.json +2 -1
- package/messages/fr/admin.json +45 -0
- package/messages/it/admin.json +45 -0
- package/messages/pt/admin.json +45 -0
- package/migrations/089_add_editor_team_role.sql +15 -23
- package/migrations/090_demo_users_teams.sql +11 -11
- package/migrations/091_greek_teams_billing.sql +15 -15
- package/migrations/093_pages_sample_data.sql +7 -7
- package/migrations/098_patterns_sample_data.sql +234 -0
- package/nextsparkjs-theme-default-0.1.0-beta.137.tgz +0 -0
- package/package.json +4 -3
- package/styles/globals.css +42 -0
- package/templates/(public)/page.tsx +1 -1
- package/tests/cypress/e2e/_utils/devtools/access.bdd.md +262 -0
- package/tests/cypress/e2e/_utils/devtools/access.cy.ts +171 -0
- package/tests/cypress/e2e/_utils/devtools/navigation.bdd.md +261 -0
- package/tests/cypress/e2e/_utils/devtools/navigation.cy.ts +157 -0
- package/tests/cypress/e2e/_utils/devtools/pages.bdd.md +303 -0
- package/tests/cypress/e2e/_utils/devtools/pages.cy.ts +184 -0
- package/tests/cypress/e2e/_utils/docs/README.md +215 -0
- package/tests/cypress/e2e/_utils/selectors/auth.bdd.md +354 -0
- package/tests/cypress/e2e/_utils/selectors/auth.cy.ts +310 -0
- package/tests/cypress/e2e/_utils/selectors/billing.bdd.md +276 -0
- package/tests/cypress/e2e/_utils/selectors/billing.cy.ts +182 -0
- package/tests/cypress/e2e/_utils/selectors/block-editor.bdd.md +615 -0
- package/tests/cypress/e2e/_utils/selectors/block-editor.cy.ts +783 -0
- package/tests/cypress/e2e/_utils/selectors/dashboard-container.cy.ts +52 -0
- package/tests/cypress/e2e/_utils/selectors/dashboard-mobile.bdd.md +205 -0
- package/tests/cypress/e2e/_utils/selectors/dashboard-mobile.cy.ts +137 -0
- package/tests/cypress/e2e/_utils/selectors/dashboard-navigation.bdd.md +147 -0
- package/tests/cypress/e2e/_utils/selectors/dashboard-navigation.cy.ts +114 -0
- package/tests/cypress/e2e/_utils/selectors/dashboard-sidebar.bdd.md +76 -0
- package/tests/cypress/e2e/_utils/selectors/dashboard-sidebar.cy.ts +68 -0
- package/tests/cypress/e2e/_utils/selectors/dashboard-topnav.bdd.md +326 -0
- package/tests/cypress/e2e/_utils/selectors/dashboard-topnav.cy.ts +177 -0
- package/tests/cypress/e2e/_utils/selectors/devtools.bdd.md +306 -0
- package/tests/cypress/e2e/_utils/selectors/devtools.cy.ts +273 -0
- package/tests/cypress/e2e/_utils/selectors/global-search.bdd.md +115 -0
- package/tests/cypress/e2e/_utils/selectors/global-search.cy.ts +93 -0
- package/tests/cypress/e2e/_utils/selectors/patterns.bdd.md +388 -0
- package/tests/cypress/e2e/_utils/selectors/patterns.cy.ts +559 -0
- package/tests/cypress/e2e/_utils/selectors/public.cy.ts +112 -0
- package/tests/cypress/e2e/_utils/selectors/settings-api-keys.bdd.md +266 -0
- package/tests/cypress/e2e/_utils/selectors/settings-api-keys.cy.ts +233 -0
- package/tests/cypress/e2e/_utils/selectors/settings-billing.bdd.md +78 -0
- package/tests/cypress/e2e/_utils/selectors/settings-billing.cy.ts +108 -0
- package/tests/cypress/e2e/_utils/selectors/settings-layout.bdd.md +129 -0
- package/tests/cypress/e2e/_utils/selectors/settings-layout.cy.ts +115 -0
- package/tests/cypress/e2e/_utils/selectors/settings-password.bdd.md +82 -0
- package/tests/cypress/e2e/_utils/selectors/settings-password.cy.ts +74 -0
- package/tests/cypress/e2e/_utils/selectors/settings-profile.bdd.md +77 -0
- package/tests/cypress/e2e/_utils/selectors/settings-profile.cy.ts +79 -0
- package/tests/cypress/e2e/_utils/selectors/settings-teams.bdd.md +130 -0
- package/tests/cypress/e2e/_utils/selectors/settings-teams.cy.ts +86 -0
- package/tests/cypress/e2e/_utils/selectors/superadmin.bdd.md +261 -0
- package/tests/cypress/e2e/_utils/selectors/superadmin.cy.ts +193 -0
- package/tests/cypress/e2e/_utils/selectors/tasks.bdd.md +593 -0
- package/tests/cypress/e2e/_utils/selectors/tasks.cy.ts +864 -0
- package/tests/cypress/e2e/_utils/selectors/taxonomies.cy.ts +126 -0
- package/tests/cypress/e2e/_utils/selectors/teams.bdd.md +278 -0
- package/tests/cypress/e2e/_utils/selectors/teams.cy.ts +195 -0
- package/tests/cypress/e2e/_utils/superadmin/all-teams.bdd.md +261 -0
- package/tests/cypress/e2e/_utils/superadmin/all-teams.cy.ts +177 -0
- package/tests/cypress/e2e/_utils/superadmin/all-users.bdd.md +406 -0
- package/tests/cypress/e2e/_utils/superadmin/all-users.cy.ts +294 -0
- package/tests/cypress/e2e/_utils/superadmin/dashboard.bdd.md +235 -0
- package/tests/cypress/e2e/_utils/superadmin/dashboard.cy.ts +149 -0
- package/tests/cypress/e2e/_utils/superadmin/subscriptions-overview.bdd.md +290 -0
- package/tests/cypress/e2e/_utils/superadmin/subscriptions-overview.cy.ts +194 -0
- package/tests/cypress/e2e/ai/ai-usage.cy.ts +209 -0
- package/tests/cypress/e2e/ai/chat-api.cy.ts +119 -0
- package/tests/cypress/e2e/ai/guardrails.cy.ts +332 -0
- package/tests/cypress/e2e/api/_core/billing/BillingAPIController.js +319 -0
- package/tests/cypress/e2e/api/_core/billing/check-action.cy.ts +326 -0
- package/tests/cypress/e2e/api/_core/billing/checkout.cy.ts +358 -0
- package/tests/cypress/e2e/api/_core/billing/lifecycle.cy.ts +423 -0
- package/tests/cypress/e2e/api/_core/billing/plans/README.md +345 -0
- package/tests/cypress/e2e/api/_core/billing/plans/business.cy.ts +412 -0
- package/tests/cypress/e2e/api/_core/billing/plans/downgrade.cy.ts +510 -0
- package/tests/cypress/e2e/api/_core/billing/plans/fixtures/billing-plans.json +163 -0
- package/tests/cypress/e2e/api/_core/billing/plans/free.cy.ts +500 -0
- package/tests/cypress/e2e/api/_core/billing/plans/pro.cy.ts +497 -0
- package/tests/cypress/e2e/api/_core/billing/plans/starter.cy.ts +342 -0
- package/tests/cypress/e2e/api/_core/billing/portal.cy.ts +313 -0
- package/tests/cypress/e2e/api/_core/devtools/registries.bdd.md +300 -0
- package/tests/cypress/e2e/api/_core/devtools/registries.cy.ts +368 -0
- package/tests/cypress/e2e/api/_core/scheduled-actions/cron-endpoint.bdd.md +375 -0
- package/tests/cypress/e2e/api/_core/scheduled-actions/cron-endpoint.cy.ts +346 -0
- package/tests/cypress/e2e/api/_core/scheduled-actions/devtools-endpoint.bdd.md +451 -0
- package/tests/cypress/e2e/api/_core/scheduled-actions/devtools-endpoint.cy.ts +447 -0
- package/tests/cypress/e2e/api/_core/scheduled-actions/scheduling.bdd.md +649 -0
- package/tests/cypress/e2e/api/_core/scheduled-actions/scheduling.cy.ts +333 -0
- package/tests/cypress/e2e/api/_core/security/security-headers.cy.ts +601 -0
- package/tests/cypress/e2e/api/_core/settings/api-keys.crud.cy.ts +923 -0
- package/tests/cypress/e2e/api/_core/teams/teams-security.cy.ts +415 -0
- package/tests/cypress/e2e/api/_core/users/users-crud.cy.ts +469 -0
- package/tests/cypress/e2e/api/_core/users/users-metas.cy.ts +913 -0
- package/tests/cypress/e2e/api/_core/users/users-security.cy.ts +375 -0
- package/tests/cypress/e2e/api/entities/customers/customers-crud.cy.ts +648 -0
- package/tests/cypress/e2e/api/entities/customers/customers-metas.cy.ts +839 -0
- package/tests/cypress/e2e/api/entities/media/media-crud.cy.ts +600 -0
- package/tests/cypress/e2e/api/entities/media/media-role-permissions.cy.ts +617 -0
- package/tests/cypress/e2e/api/entities/media/media-team-isolation.cy.ts +464 -0
- package/tests/cypress/e2e/api/entities/pages/blocks-scope.cy.ts +396 -0
- package/tests/cypress/e2e/api/entities/pages/pages-crud.cy.ts +425 -0
- package/tests/cypress/e2e/api/entities/pages/pages-status.cy.ts +335 -0
- package/tests/cypress/e2e/api/entities/posts/post-categories-crud.cy.ts +610 -0
- package/tests/cypress/e2e/api/entities/posts/posts-crud.cy.ts +709 -0
- package/tests/cypress/e2e/api/entities/posts/posts-status.cy.ts +396 -0
- package/tests/cypress/e2e/api/entities/tasks/tasks-crud.cy.ts +602 -0
- package/tests/cypress/e2e/api/entities/tasks/tasks-metas.cy.ts +878 -0
- package/tests/cypress/e2e/patterns/patterns-in-pages.cy.ts +367 -0
- package/tests/cypress/e2e/uat/_core/auth/app-roles/developer-login.bdd.md +231 -0
- package/tests/cypress/e2e/uat/_core/auth/app-roles/developer-login.cy.ts +144 -0
- package/tests/cypress/e2e/uat/_core/auth/app-roles/superadmin-login.bdd.md +118 -0
- package/tests/cypress/e2e/uat/_core/auth/app-roles/superadmin-login.cy.ts +84 -0
- package/tests/cypress/e2e/uat/_core/auth/custom-roles/editor-login.bdd.md +288 -0
- package/tests/cypress/e2e/uat/_core/auth/custom-roles/editor-login.cy.ts +188 -0
- package/tests/cypress/e2e/uat/_core/auth/login-logout.bdd.md +160 -0
- package/tests/cypress/e2e/uat/_core/auth/login-logout.cy.ts +116 -0
- package/tests/cypress/e2e/uat/_core/auth/password-reset.bdd.md +289 -0
- package/tests/cypress/e2e/uat/_core/auth/password-reset.cy.ts +200 -0
- package/tests/cypress/e2e/uat/_core/auth/registration-control-invitation.cy.ts +176 -0
- package/tests/cypress/e2e/uat/_core/auth/registration-control-open.cy.ts +131 -0
- package/tests/cypress/e2e/uat/_core/auth/registration-control.cy.ts +140 -0
- package/tests/cypress/e2e/uat/_core/auth/team-roles/admin-login.bdd.md +225 -0
- package/tests/cypress/e2e/uat/_core/auth/team-roles/admin-login.cy.ts +148 -0
- package/tests/cypress/e2e/uat/_core/auth/team-roles/member-login.bdd.md +251 -0
- package/tests/cypress/e2e/uat/_core/auth/team-roles/member-login.cy.ts +163 -0
- package/tests/cypress/e2e/uat/_core/auth/team-roles/owner-login.bdd.md +231 -0
- package/tests/cypress/e2e/uat/_core/auth/team-roles/owner-login.cy.ts +141 -0
- package/tests/cypress/e2e/uat/_core/billing/extended.bdd.md +273 -0
- package/tests/cypress/e2e/uat/_core/billing/extended.cy.ts +209 -0
- package/tests/cypress/e2e/uat/_core/billing/feature-gates.bdd.md +407 -0
- package/tests/cypress/e2e/uat/_core/billing/feature-gates.cy.ts +307 -0
- package/tests/cypress/e2e/uat/_core/billing/page.bdd.md +329 -0
- package/tests/cypress/e2e/uat/_core/billing/page.cy.ts +250 -0
- package/tests/cypress/e2e/uat/_core/billing/status.bdd.md +190 -0
- package/tests/cypress/e2e/uat/_core/billing/status.cy.ts +145 -0
- package/tests/cypress/e2e/uat/_core/billing/team-switch.bdd.md +156 -0
- package/tests/cypress/e2e/uat/_core/billing/team-switch.cy.ts +122 -0
- package/tests/cypress/e2e/uat/_core/billing/usage.bdd.md +218 -0
- package/tests/cypress/e2e/uat/_core/billing/usage.cy.ts +176 -0
- package/tests/cypress/e2e/uat/_core/blocks/hero.bdd.md +124 -0
- package/tests/cypress/e2e/uat/_core/blocks/hero.cy.ts +56 -0
- package/tests/cypress/e2e/uat/_core/devtools/api-tester.cy.ts +390 -0
- package/tests/cypress/e2e/uat/_core/performance/suspense-loading.cy.ts +134 -0
- package/tests/cypress/e2e/uat/_core/scheduled-actions/devtools-ui.bdd.md +736 -0
- package/tests/cypress/e2e/uat/_core/scheduled-actions/devtools-ui.cy.ts +740 -0
- package/tests/cypress/e2e/uat/_core/teams/inline-edit.cy.ts +278 -0
- package/tests/cypress/e2e/uat/_core/teams/roles-matrix.bdd.md +553 -0
- package/tests/cypress/e2e/uat/_core/teams/roles-matrix.cy.ts +185 -0
- package/tests/cypress/e2e/uat/_core/teams/switcher.bdd.md +1151 -0
- package/tests/cypress/e2e/uat/_core/teams/switcher.cy.ts +497 -0
- package/tests/cypress/e2e/uat/_core/teams/team-switcher.md +198 -0
- package/tests/cypress/e2e/uat/entities/customers/member.bdd.md +275 -0
- package/tests/cypress/e2e/uat/entities/customers/member.cy.ts +122 -0
- package/tests/cypress/e2e/uat/entities/customers/owner.bdd.md +243 -0
- package/tests/cypress/e2e/uat/entities/customers/owner.cy.ts +165 -0
- package/tests/cypress/e2e/uat/entities/pages/block-crud.bdd.md +476 -0
- package/tests/cypress/e2e/uat/entities/pages/block-crud.cy.ts +486 -0
- package/tests/cypress/e2e/uat/entities/pages/block-editor.bdd.md +460 -0
- package/tests/cypress/e2e/uat/entities/pages/block-editor.cy.ts +301 -0
- package/tests/cypress/e2e/uat/entities/pages/list.bdd.md +432 -0
- package/tests/cypress/e2e/uat/entities/pages/list.cy.ts +273 -0
- package/tests/cypress/e2e/uat/entities/pages/public-rendering.bdd.md +696 -0
- package/tests/cypress/e2e/uat/entities/pages/public-rendering.cy.ts +340 -0
- package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.bdd.md +161 -0
- package/tests/cypress/e2e/uat/entities/posts/categories-api-aware.cy.ts +104 -0
- package/tests/cypress/e2e/uat/entities/posts/categories.bdd.md +375 -0
- package/tests/cypress/e2e/uat/entities/posts/categories.cy.ts +241 -0
- package/tests/cypress/e2e/uat/entities/posts/editor.bdd.md +429 -0
- package/tests/cypress/e2e/uat/entities/posts/editor.cy.ts +257 -0
- package/tests/cypress/e2e/uat/entities/posts/list.bdd.md +340 -0
- package/tests/cypress/e2e/uat/entities/posts/list.cy.ts +177 -0
- package/tests/cypress/e2e/uat/entities/posts/public.bdd.md +614 -0
- package/tests/cypress/e2e/uat/entities/posts/public.cy.ts +249 -0
- package/tests/cypress/e2e/uat/entities/tasks/member.bdd.md +222 -0
- package/tests/cypress/e2e/uat/entities/tasks/member.cy.ts +165 -0
- package/tests/cypress/e2e/uat/entities/tasks/owner.bdd.md +419 -0
- package/tests/cypress/e2e/uat/entities/tasks/owner.cy.ts +191 -0
- package/tests/cypress/e2e/uat/features/roles/editor-role.bdd.md +552 -0
- package/tests/cypress/e2e/uat/features/roles/editor-role.cy.ts +210 -0
- package/tests/cypress/e2e/uat/features/roles/member-restrictions.bdd.md +450 -0
- package/tests/cypress/e2e/uat/features/roles/member-restrictions.cy.ts +189 -0
- package/tests/cypress/e2e/uat/features/roles/owner-full-crud.bdd.md +530 -0
- package/tests/cypress/e2e/uat/features/roles/owner-full-crud.cy.ts +247 -0
- package/tests/cypress/fixtures/blocks.json +218 -0
- package/tests/cypress/fixtures/entities.json +87 -0
- package/tests/cypress/fixtures/page-builder.json +21 -0
- package/tests/cypress/src/components/CategoriesPOM.ts +382 -0
- package/tests/cypress/src/components/CustomersPOM.ts +439 -0
- package/tests/cypress/src/components/DevKeyringPOM.ts +160 -0
- package/tests/cypress/src/components/EntityForm.ts +375 -0
- package/tests/cypress/src/components/EntityList.ts +389 -0
- package/tests/cypress/src/components/PageBuilderPOM.ts +710 -0
- package/tests/cypress/src/components/PostEditorPOM.ts +370 -0
- package/tests/cypress/src/components/PostsListPOM.ts +223 -0
- package/tests/cypress/src/components/PublicPagePOM.ts +447 -0
- package/tests/cypress/src/components/PublicPostPOM.ts +146 -0
- package/tests/cypress/src/components/TasksPOM.ts +272 -0
- package/tests/cypress/src/components/TeamSwitcherPOM.ts +450 -0
- package/tests/cypress/src/components/index.ts +21 -0
- package/tests/cypress/src/controllers/ApiKeysAPIController.js +178 -0
- package/tests/cypress/src/controllers/BaseAPIController.js +317 -0
- package/tests/cypress/src/controllers/CustomerAPIController.js +251 -0
- package/tests/cypress/src/controllers/MediaAPIController.js +231 -0
- package/tests/cypress/src/controllers/PagesAPIController.js +226 -0
- package/tests/cypress/src/controllers/PostsAPIController.js +250 -0
- package/tests/cypress/src/controllers/TaskAPIController.js +240 -0
- package/tests/cypress/src/controllers/UsersAPIController.js +242 -0
- package/tests/cypress/src/controllers/index.js +25 -0
- package/tests/cypress/src/core/AuthPOM.ts +450 -0
- package/tests/cypress/src/core/BasePOM.ts +33 -0
- package/tests/cypress/src/core/BlockEditorBasePOM.ts +874 -0
- package/tests/cypress/src/core/DashboardEntityPOM.ts +41 -0
- package/tests/cypress/src/core/index.ts +14 -0
- package/tests/cypress/src/entities/CustomersPOM.ts +172 -0
- package/tests/cypress/src/entities/PagesPOM.ts +137 -0
- package/tests/cypress/src/entities/PatternsPOM.ts +329 -0
- package/tests/cypress/src/entities/PostsPOM.ts +137 -0
- package/tests/cypress/src/entities/TasksPOM.ts +246 -0
- package/tests/cypress/src/entities/index.ts +16 -0
- package/tests/cypress/src/features/BillingPOM.ts +385 -0
- package/tests/cypress/src/features/DashboardPOM.ts +271 -0
- package/tests/cypress/src/features/DevtoolsPOM.ts +750 -0
- package/tests/cypress/src/features/PageBuilderPOM.ts +283 -0
- package/tests/cypress/src/features/PostEditorPOM.ts +313 -0
- package/tests/cypress/src/features/ScheduledActionsPOM.ts +463 -0
- package/tests/cypress/src/features/SettingsPOM.ts +707 -0
- package/tests/cypress/src/features/SuperadminPOM.ts +851 -0
- package/tests/cypress/src/features/SuperadminTeamRolesPOM.ts +285 -0
- package/tests/cypress/src/features/index.ts +28 -0
- package/tests/cypress/src/helpers/ApiInterceptor.ts +20 -0
- package/tests/cypress/src/index.ts +101 -0
- package/tests/cypress/src/pages/dashboard/Dashboard.js +677 -0
- package/tests/cypress/src/pages/dashboard/DashboardPage.js +43 -0
- package/tests/cypress/src/pages/dashboard/DashboardStats.js +546 -0
- package/tests/cypress/src/pages/dashboard/index.js +6 -0
- package/tests/cypress/src/pages/index.js +5 -0
- package/tests/cypress/src/pages/public/FeaturesPage.js +28 -0
- package/tests/cypress/src/pages/public/LandingPage.js +69 -0
- package/tests/cypress/src/pages/public/PricingPage.js +33 -0
- package/tests/cypress/src/pages/public/index.js +6 -0
- package/tests/cypress/src/selectors.ts +46 -0
- package/tests/cypress/src/session-helpers.ts +518 -0
- package/tests/cypress/support/doc-commands.ts +260 -0
- package/tests/cypress/support/e2e.ts +90 -0
- package/tests/cypress.config.ts +178 -0
- package/tests/jest/__mocks__/@nextsparkjs/core/components/ui/badge.js +16 -0
- package/tests/jest/__mocks__/@nextsparkjs/core/lib/db.js +11 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +160 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/theme-registry.ts +68 -0
- package/tests/jest/__mocks__/jose.js +22 -0
- package/tests/jest/__mocks__/next/image.js +15 -0
- package/tests/jest/__mocks__/next-server.js +56 -0
- package/tests/jest/components/post-header.test.tsx +377 -0
- package/tests/jest/jest.config.cjs +154 -0
- package/tests/jest/langchain/COVERAGE.md +372 -0
- package/tests/jest/langchain/guardrails.test.ts +465 -0
- package/tests/jest/langchain/streaming.test.ts +370 -0
- package/tests/jest/langchain/token-tracker.test.ts +455 -0
- package/tests/jest/langchain/tracer-callbacks.test.ts +881 -0
- package/tests/jest/langchain/tracer.test.ts +823 -0
- package/tests/jest/services/tasks.service.test.ts +707 -0
- package/tests/jest/setup.ts +170 -0
- package/tests/jest/tsconfig.jest.json +6 -0
- package/tests/jest/validation/categories.test.ts +429 -0
- package/tests/jest/validation/posts.test.ts +546 -0
- package/tests/tsconfig.json +21 -0
- /package/docs/{02-features → public/02-features}/01-components.md +0 -0
- /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,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
|
+
})
|