@nextsparkjs/theme-default 0.1.0-beta.20 → 0.1.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/tests/cypress/e2e/_devtools/access.bdd.md +262 -0
- package/tests/cypress/e2e/_devtools/access.cy.ts +171 -0
- package/tests/cypress/e2e/_devtools/navigation.bdd.md +261 -0
- package/tests/cypress/e2e/_devtools/navigation.cy.ts +157 -0
- package/tests/cypress/e2e/_devtools/pages.bdd.md +303 -0
- package/tests/cypress/e2e/_devtools/pages.cy.ts +184 -0
- package/tests/cypress/e2e/_docs/README.md +215 -0
- package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin-teams.narration.json +155 -0
- package/tests/cypress/e2e/_docs/tutorials/sector7-superadmin.cy.ts +390 -0
- package/tests/cypress/e2e/_docs/tutorials/teams-system.doc.cy.ts +349 -0
- package/tests/cypress/e2e/_docs/tutorials/teams-system.narration.json +165 -0
- package/tests/cypress/e2e/_selectors/auth.cy.ts +306 -0
- package/tests/cypress/e2e/_selectors/billing.cy.ts +89 -0
- package/tests/cypress/e2e/_selectors/dashboard-mobile.cy.ts +113 -0
- package/tests/cypress/e2e/_selectors/dashboard-navigation.cy.ts +89 -0
- package/tests/cypress/e2e/_selectors/dashboard-sidebar.cy.ts +60 -0
- package/tests/cypress/e2e/_selectors/dashboard-topnav.cy.ts +146 -0
- package/tests/cypress/e2e/_selectors/devtools.cy.ts +210 -0
- package/tests/cypress/e2e/_selectors/global-search.cy.ts +88 -0
- package/tests/cypress/e2e/_selectors/pages-editor.cy.ts +179 -0
- package/tests/cypress/e2e/_selectors/posts-editor.cy.ts +282 -0
- package/tests/cypress/e2e/_selectors/public.cy.ts +112 -0
- package/tests/cypress/e2e/_selectors/settings-api-keys.cy.ts +228 -0
- package/tests/cypress/e2e/_selectors/settings-billing.cy.ts +105 -0
- package/tests/cypress/e2e/_selectors/settings-layout.cy.ts +119 -0
- package/tests/cypress/e2e/_selectors/settings-password.cy.ts +71 -0
- package/tests/cypress/e2e/_selectors/settings-profile.cy.ts +82 -0
- package/tests/cypress/e2e/_selectors/settings-teams.cy.ts +68 -0
- package/tests/cypress/e2e/_selectors/superadmin.cy.ts +185 -0
- package/tests/cypress/e2e/_selectors/tasks.cy.ts +242 -0
- package/tests/cypress/e2e/_selectors/taxonomies.cy.ts +126 -0
- package/tests/cypress/e2e/_selectors/teams.cy.ts +142 -0
- package/tests/cypress/e2e/_superadmin/all-teams.bdd.md +261 -0
- package/tests/cypress/e2e/_superadmin/all-teams.cy.ts +177 -0
- package/tests/cypress/e2e/_superadmin/all-users.bdd.md +406 -0
- package/tests/cypress/e2e/_superadmin/all-users.cy.ts +294 -0
- package/tests/cypress/e2e/_superadmin/dashboard.bdd.md +235 -0
- package/tests/cypress/e2e/_superadmin/dashboard.cy.ts +149 -0
- package/tests/cypress/e2e/_superadmin/subscriptions-overview.bdd.md +290 -0
- package/tests/cypress/e2e/_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 +107 -0
- package/tests/cypress/e2e/ai/guardrails.cy.ts +332 -0
- package/tests/cypress/e2e/api/billing/BillingAPIController.js +319 -0
- package/tests/cypress/e2e/api/billing/check-action.cy.ts +326 -0
- package/tests/cypress/e2e/api/billing/checkout.cy.ts +358 -0
- package/tests/cypress/e2e/api/billing/lifecycle.cy.ts +423 -0
- package/tests/cypress/e2e/api/billing/plans/README.md +345 -0
- package/tests/cypress/e2e/api/billing/plans/business.cy.ts +412 -0
- package/tests/cypress/e2e/api/billing/plans/downgrade.cy.ts +510 -0
- package/tests/cypress/e2e/api/billing/plans/fixtures/billing-plans.json +163 -0
- package/tests/cypress/e2e/api/billing/plans/free.cy.ts +500 -0
- package/tests/cypress/e2e/api/billing/plans/pro.cy.ts +497 -0
- package/tests/cypress/e2e/api/billing/plans/starter.cy.ts +342 -0
- package/tests/cypress/e2e/api/billing/portal.cy.ts +313 -0
- package/tests/cypress/e2e/api/devtools/registries.bdd.md +300 -0
- package/tests/cypress/e2e/api/devtools/registries.cy.ts +368 -0
- package/tests/cypress/e2e/api/entities/blocks-scope.cy.ts +396 -0
- package/tests/cypress/e2e/api/entities/customers-crud.cy.ts +648 -0
- package/tests/cypress/e2e/api/entities/customers-metas.cy.ts +839 -0
- package/tests/cypress/e2e/api/entities/pages-crud.cy.ts +425 -0
- package/tests/cypress/e2e/api/entities/pages-status.cy.ts +335 -0
- package/tests/cypress/e2e/api/entities/post-categories-crud.cy.ts +610 -0
- package/tests/cypress/e2e/api/entities/posts-crud.cy.ts +709 -0
- package/tests/cypress/e2e/api/entities/posts-status.cy.ts +396 -0
- package/tests/cypress/e2e/api/entities/tasks-crud.cy.ts +602 -0
- package/tests/cypress/e2e/api/entities/tasks-metas.cy.ts +878 -0
- package/tests/cypress/e2e/api/entities/users-crud.cy.ts +469 -0
- package/tests/cypress/e2e/api/entities/users-metas.cy.ts +913 -0
- package/tests/cypress/e2e/api/entities/users-security.cy.ts +375 -0
- package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.bdd.md +375 -0
- package/tests/cypress/e2e/api/scheduled-actions/cron-endpoint.cy.ts +346 -0
- package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.bdd.md +451 -0
- package/tests/cypress/e2e/api/scheduled-actions/devtools-endpoint.cy.ts +447 -0
- package/tests/cypress/e2e/api/scheduled-actions/scheduling.bdd.md +649 -0
- package/tests/cypress/e2e/api/scheduled-actions/scheduling.cy.ts +333 -0
- package/tests/cypress/e2e/api/settings/api-keys.crud.cy.ts +923 -0
- package/tests/cypress/e2e/uat/auth/app-roles/developer-login.bdd.md +231 -0
- package/tests/cypress/e2e/uat/auth/app-roles/developer-login.cy.ts +144 -0
- package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.bdd.md +118 -0
- package/tests/cypress/e2e/uat/auth/app-roles/superadmin-login.cy.ts +84 -0
- package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.bdd.md +288 -0
- package/tests/cypress/e2e/uat/auth/custom-roles/editor-login.cy.ts +188 -0
- package/tests/cypress/e2e/uat/auth/login-logout.bdd.md +160 -0
- package/tests/cypress/e2e/uat/auth/login-logout.cy.ts +116 -0
- package/tests/cypress/e2e/uat/auth/password-reset.bdd.md +289 -0
- package/tests/cypress/e2e/uat/auth/password-reset.cy.ts +200 -0
- package/tests/cypress/e2e/uat/auth/team-roles/admin-login.bdd.md +225 -0
- package/tests/cypress/e2e/uat/auth/team-roles/admin-login.cy.ts +148 -0
- package/tests/cypress/e2e/uat/auth/team-roles/member-login.bdd.md +251 -0
- package/tests/cypress/e2e/uat/auth/team-roles/member-login.cy.ts +163 -0
- package/tests/cypress/e2e/uat/auth/team-roles/owner-login.bdd.md +231 -0
- package/tests/cypress/e2e/uat/auth/team-roles/owner-login.cy.ts +141 -0
- package/tests/cypress/e2e/uat/billing/extended.bdd.md +273 -0
- package/tests/cypress/e2e/uat/billing/extended.cy.ts +209 -0
- package/tests/cypress/e2e/uat/billing/feature-gates.bdd.md +407 -0
- package/tests/cypress/e2e/uat/billing/feature-gates.cy.ts +307 -0
- package/tests/cypress/e2e/uat/billing/page.bdd.md +329 -0
- package/tests/cypress/e2e/uat/billing/page.cy.ts +250 -0
- package/tests/cypress/e2e/uat/billing/status.bdd.md +190 -0
- package/tests/cypress/e2e/uat/billing/status.cy.ts +145 -0
- package/tests/cypress/e2e/uat/billing/team-switch.bdd.md +156 -0
- package/tests/cypress/e2e/uat/billing/team-switch.cy.ts +122 -0
- package/tests/cypress/e2e/uat/billing/usage.bdd.md +218 -0
- package/tests/cypress/e2e/uat/billing/usage.cy.ts +176 -0
- package/tests/cypress/e2e/uat/blocks/hero.bdd.md +124 -0
- package/tests/cypress/e2e/uat/blocks/hero.cy.ts +56 -0
- package/tests/cypress/e2e/uat/devtools/api-tester.cy.ts +390 -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/roles/editor-role.bdd.md +552 -0
- package/tests/cypress/e2e/uat/roles/editor-role.cy.ts +210 -0
- package/tests/cypress/e2e/uat/roles/member-restrictions.bdd.md +450 -0
- package/tests/cypress/e2e/uat/roles/member-restrictions.cy.ts +189 -0
- package/tests/cypress/e2e/uat/roles/owner-full-crud.bdd.md +530 -0
- package/tests/cypress/e2e/uat/roles/owner-full-crud.cy.ts +247 -0
- package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.bdd.md +736 -0
- package/tests/cypress/e2e/uat/scheduled-actions/devtools-ui.cy.ts +740 -0
- package/tests/cypress/e2e/uat/teams/roles-matrix.bdd.md +553 -0
- package/tests/cypress/e2e/uat/teams/roles-matrix.cy.ts +185 -0
- package/tests/cypress/e2e/uat/teams/switcher.bdd.md +1151 -0
- package/tests/cypress/e2e/uat/teams/switcher.cy.ts +497 -0
- package/tests/cypress/e2e/uat/teams/team-switcher.md +198 -0
- package/tests/cypress/fixtures/blocks.json +218 -0
- package/tests/cypress/fixtures/entities.json +78 -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/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 +86 -0
- package/tests/cypress/src/core/BlockEditorBasePOM.ts +576 -0
- package/tests/cypress/src/core/DashboardEntityPOM.ts +692 -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/PostsPOM.ts +137 -0
- package/tests/cypress/src/entities/TasksPOM.ts +176 -0
- package/tests/cypress/src/entities/index.ts +14 -0
- package/tests/cypress/src/features/BillingPOM.ts +385 -0
- package/tests/cypress/src/features/DashboardPOM.ts +245 -0
- package/tests/cypress/src/features/DevtoolsPOM.ts +739 -0
- package/tests/cypress/src/features/PageBuilderPOM.ts +263 -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 +362 -0
- package/tests/cypress/src/features/SuperadminPOM.ts +331 -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 +177 -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 +500 -0
- package/tests/cypress/support/doc-commands.ts +260 -0
- package/tests/cypress.config.ts +150 -0
- package/tests/jest/components/post-header.test.tsx +377 -0
- package/tests/jest/config/role-config.test.ts +529 -0
- package/tests/jest/jest.config.ts +81 -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 +367 -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/user-roles/role-helpers.test.ts +432 -0
- package/tests/jest/validation/categories.test.ts +429 -0
- package/tests/jest/validation/posts.test.ts +546 -0
- package/tests/tsconfig.json +15 -0
|
@@ -0,0 +1,546 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit Tests - Posts Validation Schemas
|
|
3
|
+
*
|
|
4
|
+
* Tests Zod validation schemas for posts API:
|
|
5
|
+
* - createPostSchema: Required fields, slug format, URL validation, block structure
|
|
6
|
+
* - Edge cases: Empty strings, max lengths, invalid formats
|
|
7
|
+
*
|
|
8
|
+
* Focus on business logic validation WITHOUT database calls.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { z } from 'zod'
|
|
12
|
+
|
|
13
|
+
// Copy schemas from API route to test in isolation
|
|
14
|
+
const createPostSchema = z.object({
|
|
15
|
+
slug: z.string().min(2).max(100).regex(/^[a-z0-9-]+$/, 'Slug must be lowercase alphanumeric with dashes'),
|
|
16
|
+
title: z.string().min(1).max(255),
|
|
17
|
+
excerpt: z.string().max(500).optional(),
|
|
18
|
+
featuredImage: z.string().url().max(500).optional().or(z.literal('')),
|
|
19
|
+
blocks: z.array(z.object({
|
|
20
|
+
id: z.string(),
|
|
21
|
+
blockSlug: z.string(),
|
|
22
|
+
props: z.record(z.string(), z.unknown())
|
|
23
|
+
})).default([]),
|
|
24
|
+
locale: z.string().default('en'),
|
|
25
|
+
seoTitle: z.string().max(255).optional(),
|
|
26
|
+
seoDescription: z.string().optional(),
|
|
27
|
+
seoKeywords: z.string().optional(),
|
|
28
|
+
ogImage: z.string().url().max(500).optional().or(z.literal('')),
|
|
29
|
+
noindex: z.boolean().default(false),
|
|
30
|
+
nofollow: z.boolean().default(false),
|
|
31
|
+
published: z.boolean().default(false),
|
|
32
|
+
categoryIds: z.array(z.string()).optional()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('Posts Validation Schemas', () => {
|
|
36
|
+
describe('createPostSchema - Valid Data', () => {
|
|
37
|
+
it('should accept minimal valid post data', () => {
|
|
38
|
+
const validData = {
|
|
39
|
+
slug: 'test-post',
|
|
40
|
+
title: 'Test Post'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result = createPostSchema.safeParse(validData)
|
|
44
|
+
|
|
45
|
+
expect(result.success).toBe(true)
|
|
46
|
+
if (result.success) {
|
|
47
|
+
expect(result.data.slug).toBe('test-post')
|
|
48
|
+
expect(result.data.title).toBe('Test Post')
|
|
49
|
+
expect(result.data.blocks).toEqual([]) // Default value
|
|
50
|
+
expect(result.data.locale).toBe('en') // Default value
|
|
51
|
+
expect(result.data.published).toBe(false) // Default value
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should accept complete post data with all fields', () => {
|
|
56
|
+
const completeData = {
|
|
57
|
+
slug: 'complete-post',
|
|
58
|
+
title: 'Complete Post',
|
|
59
|
+
excerpt: 'This is an excerpt',
|
|
60
|
+
featuredImage: 'https://example.com/image.jpg',
|
|
61
|
+
blocks: [
|
|
62
|
+
{
|
|
63
|
+
id: 'block-1',
|
|
64
|
+
blockSlug: 'hero',
|
|
65
|
+
props: { title: 'Hero Title' }
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
locale: 'es',
|
|
69
|
+
seoTitle: 'SEO Title',
|
|
70
|
+
seoDescription: 'SEO Description',
|
|
71
|
+
seoKeywords: 'keyword1, keyword2',
|
|
72
|
+
ogImage: 'https://example.com/og-image.jpg',
|
|
73
|
+
noindex: true,
|
|
74
|
+
nofollow: true,
|
|
75
|
+
published: true,
|
|
76
|
+
categoryIds: ['cat-1', 'cat-2']
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const result = createPostSchema.safeParse(completeData)
|
|
80
|
+
|
|
81
|
+
expect(result.success).toBe(true)
|
|
82
|
+
if (result.success) {
|
|
83
|
+
expect(result.data.slug).toBe('complete-post')
|
|
84
|
+
expect(result.data.title).toBe('Complete Post')
|
|
85
|
+
expect(result.data.excerpt).toBe('This is an excerpt')
|
|
86
|
+
expect(result.data.featuredImage).toBe('https://example.com/image.jpg')
|
|
87
|
+
expect(result.data.blocks).toHaveLength(1)
|
|
88
|
+
expect(result.data.locale).toBe('es')
|
|
89
|
+
expect(result.data.published).toBe(true)
|
|
90
|
+
expect(result.data.categoryIds).toEqual(['cat-1', 'cat-2'])
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('should accept empty blocks array', () => {
|
|
95
|
+
const data = {
|
|
96
|
+
slug: 'no-blocks',
|
|
97
|
+
title: 'Post Without Blocks',
|
|
98
|
+
blocks: []
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const result = createPostSchema.safeParse(data)
|
|
102
|
+
|
|
103
|
+
expect(result.success).toBe(true)
|
|
104
|
+
if (result.success) {
|
|
105
|
+
expect(result.data.blocks).toEqual([])
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('should accept empty string for featuredImage', () => {
|
|
110
|
+
const data = {
|
|
111
|
+
slug: 'test-post',
|
|
112
|
+
title: 'Test',
|
|
113
|
+
featuredImage: ''
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const result = createPostSchema.safeParse(data)
|
|
117
|
+
|
|
118
|
+
expect(result.success).toBe(true)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('should accept empty string for ogImage', () => {
|
|
122
|
+
const data = {
|
|
123
|
+
slug: 'test-post',
|
|
124
|
+
title: 'Test',
|
|
125
|
+
ogImage: ''
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const result = createPostSchema.safeParse(data)
|
|
129
|
+
|
|
130
|
+
expect(result.success).toBe(true)
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
describe('createPostSchema - Slug Validation', () => {
|
|
135
|
+
it('should reject empty slug', () => {
|
|
136
|
+
const data = {
|
|
137
|
+
slug: '',
|
|
138
|
+
title: 'Test'
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const result = createPostSchema.safeParse(data)
|
|
142
|
+
|
|
143
|
+
expect(result.success).toBe(false)
|
|
144
|
+
if (!result.success) {
|
|
145
|
+
expect(result.error.issues[0].path).toContain('slug')
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('should reject slug shorter than 2 characters', () => {
|
|
150
|
+
const data = {
|
|
151
|
+
slug: 'a',
|
|
152
|
+
title: 'Test'
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const result = createPostSchema.safeParse(data)
|
|
156
|
+
|
|
157
|
+
expect(result.success).toBe(false)
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('should reject slug longer than 100 characters', () => {
|
|
161
|
+
const data = {
|
|
162
|
+
slug: 'a'.repeat(101),
|
|
163
|
+
title: 'Test'
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const result = createPostSchema.safeParse(data)
|
|
167
|
+
|
|
168
|
+
expect(result.success).toBe(false)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('should reject slug with uppercase letters', () => {
|
|
172
|
+
const data = {
|
|
173
|
+
slug: 'Test-Post',
|
|
174
|
+
title: 'Test'
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const result = createPostSchema.safeParse(data)
|
|
178
|
+
|
|
179
|
+
expect(result.success).toBe(false)
|
|
180
|
+
if (!result.success) {
|
|
181
|
+
expect(result.error.issues[0].message).toContain('lowercase')
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
it('should reject slug with spaces', () => {
|
|
186
|
+
const data = {
|
|
187
|
+
slug: 'test post',
|
|
188
|
+
title: 'Test'
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const result = createPostSchema.safeParse(data)
|
|
192
|
+
|
|
193
|
+
expect(result.success).toBe(false)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('should reject slug with special characters', () => {
|
|
197
|
+
const invalidSlugs = [
|
|
198
|
+
'test_post', // underscore
|
|
199
|
+
'test.post', // period
|
|
200
|
+
'test@post', // at symbol
|
|
201
|
+
'test!post', // exclamation
|
|
202
|
+
'test#post', // hash
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
invalidSlugs.forEach(slug => {
|
|
206
|
+
const result = createPostSchema.safeParse({ slug, title: 'Test' })
|
|
207
|
+
expect(result.success).toBe(false)
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
it('should accept slug with numbers and dashes', () => {
|
|
212
|
+
const validSlugs = [
|
|
213
|
+
'test-post-123',
|
|
214
|
+
'2024-01-01',
|
|
215
|
+
'post-1',
|
|
216
|
+
'hello-world-123-test',
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
validSlugs.forEach(slug => {
|
|
220
|
+
const result = createPostSchema.safeParse({ slug, title: 'Test' })
|
|
221
|
+
expect(result.success).toBe(true)
|
|
222
|
+
})
|
|
223
|
+
})
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
describe('createPostSchema - Title Validation', () => {
|
|
227
|
+
it('should reject empty title', () => {
|
|
228
|
+
const data = {
|
|
229
|
+
slug: 'test',
|
|
230
|
+
title: ''
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const result = createPostSchema.safeParse(data)
|
|
234
|
+
|
|
235
|
+
expect(result.success).toBe(false)
|
|
236
|
+
if (!result.success) {
|
|
237
|
+
expect(result.error.issues[0].path).toContain('title')
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('should reject title longer than 255 characters', () => {
|
|
242
|
+
const data = {
|
|
243
|
+
slug: 'test',
|
|
244
|
+
title: 'a'.repeat(256)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const result = createPostSchema.safeParse(data)
|
|
248
|
+
|
|
249
|
+
expect(result.success).toBe(false)
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
it('should accept title with exactly 255 characters', () => {
|
|
253
|
+
const data = {
|
|
254
|
+
slug: 'test',
|
|
255
|
+
title: 'a'.repeat(255)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const result = createPostSchema.safeParse(data)
|
|
259
|
+
|
|
260
|
+
expect(result.success).toBe(true)
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
describe('createPostSchema - Excerpt Validation', () => {
|
|
265
|
+
it('should accept excerpt with 500 characters', () => {
|
|
266
|
+
const data = {
|
|
267
|
+
slug: 'test',
|
|
268
|
+
title: 'Test',
|
|
269
|
+
excerpt: 'a'.repeat(500)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const result = createPostSchema.safeParse(data)
|
|
273
|
+
|
|
274
|
+
expect(result.success).toBe(true)
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it('should reject excerpt longer than 500 characters', () => {
|
|
278
|
+
const data = {
|
|
279
|
+
slug: 'test',
|
|
280
|
+
title: 'Test',
|
|
281
|
+
excerpt: 'a'.repeat(501)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const result = createPostSchema.safeParse(data)
|
|
285
|
+
|
|
286
|
+
expect(result.success).toBe(false)
|
|
287
|
+
if (!result.success) {
|
|
288
|
+
expect(result.error.issues[0].path).toContain('excerpt')
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
it('should accept missing excerpt (optional)', () => {
|
|
293
|
+
const data = {
|
|
294
|
+
slug: 'test',
|
|
295
|
+
title: 'Test'
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const result = createPostSchema.safeParse(data)
|
|
299
|
+
|
|
300
|
+
expect(result.success).toBe(true)
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
describe('createPostSchema - URL Validation', () => {
|
|
305
|
+
it('should reject invalid featuredImage URL', () => {
|
|
306
|
+
const invalidUrls = [
|
|
307
|
+
'not-a-url',
|
|
308
|
+
'example.com/image.jpg', // missing protocol
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
invalidUrls.forEach(url => {
|
|
312
|
+
const result = createPostSchema.safeParse({
|
|
313
|
+
slug: 'test',
|
|
314
|
+
title: 'Test',
|
|
315
|
+
featuredImage: url
|
|
316
|
+
})
|
|
317
|
+
expect(result.success).toBe(false)
|
|
318
|
+
})
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
it('should accept ftp URL (Zod url() accepts any valid URL scheme)', () => {
|
|
322
|
+
// Note: Zod z.string().url() accepts any valid URL including ftp://
|
|
323
|
+
// If http/https only is required, use .regex() instead
|
|
324
|
+
const result = createPostSchema.safeParse({
|
|
325
|
+
slug: 'test',
|
|
326
|
+
title: 'Test',
|
|
327
|
+
featuredImage: 'ftp://example.com/image.jpg'
|
|
328
|
+
})
|
|
329
|
+
expect(result.success).toBe(true)
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
it('should accept valid featuredImage URLs', () => {
|
|
333
|
+
const validUrls = [
|
|
334
|
+
'https://example.com/image.jpg',
|
|
335
|
+
'http://example.com/image.png',
|
|
336
|
+
'https://cdn.example.com/path/to/image.webp',
|
|
337
|
+
]
|
|
338
|
+
|
|
339
|
+
validUrls.forEach(url => {
|
|
340
|
+
const result = createPostSchema.safeParse({
|
|
341
|
+
slug: 'test',
|
|
342
|
+
title: 'Test',
|
|
343
|
+
featuredImage: url
|
|
344
|
+
})
|
|
345
|
+
expect(result.success).toBe(true)
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
it('should reject invalid ogImage URL', () => {
|
|
350
|
+
const result = createPostSchema.safeParse({
|
|
351
|
+
slug: 'test',
|
|
352
|
+
title: 'Test',
|
|
353
|
+
ogImage: 'not-a-url'
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
expect(result.success).toBe(false)
|
|
357
|
+
})
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
describe('createPostSchema - Blocks Validation', () => {
|
|
361
|
+
it('should require id, blockSlug, and props in each block', () => {
|
|
362
|
+
const data = {
|
|
363
|
+
slug: 'test',
|
|
364
|
+
title: 'Test',
|
|
365
|
+
blocks: [
|
|
366
|
+
{ id: 'b1', blockSlug: 'hero', props: {} }
|
|
367
|
+
]
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const result = createPostSchema.safeParse(data)
|
|
371
|
+
|
|
372
|
+
expect(result.success).toBe(true)
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
it('should reject blocks missing required fields', () => {
|
|
376
|
+
const invalidBlocks = [
|
|
377
|
+
[{ blockSlug: 'hero', props: {} }], // missing id
|
|
378
|
+
[{ id: 'b1', props: {} }], // missing blockSlug
|
|
379
|
+
[{ id: 'b1', blockSlug: 'hero' }], // missing props
|
|
380
|
+
]
|
|
381
|
+
|
|
382
|
+
invalidBlocks.forEach(blocks => {
|
|
383
|
+
const result = createPostSchema.safeParse({
|
|
384
|
+
slug: 'test',
|
|
385
|
+
title: 'Test',
|
|
386
|
+
blocks
|
|
387
|
+
})
|
|
388
|
+
expect(result.success).toBe(false)
|
|
389
|
+
})
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
it('should accept blocks with complex props', () => {
|
|
393
|
+
const data = {
|
|
394
|
+
slug: 'test',
|
|
395
|
+
title: 'Test',
|
|
396
|
+
blocks: [
|
|
397
|
+
{
|
|
398
|
+
id: 'block-1',
|
|
399
|
+
blockSlug: 'hero',
|
|
400
|
+
props: {
|
|
401
|
+
title: 'Hero Title',
|
|
402
|
+
subtitle: 'Subtitle',
|
|
403
|
+
nested: { value: 123 },
|
|
404
|
+
array: [1, 2, 3]
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
]
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const result = createPostSchema.safeParse(data)
|
|
411
|
+
|
|
412
|
+
expect(result.success).toBe(true)
|
|
413
|
+
})
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
describe('createPostSchema - SEO Validation', () => {
|
|
417
|
+
it('should accept valid SEO fields', () => {
|
|
418
|
+
const data = {
|
|
419
|
+
slug: 'test',
|
|
420
|
+
title: 'Test',
|
|
421
|
+
seoTitle: 'SEO Title',
|
|
422
|
+
seoDescription: 'SEO Description',
|
|
423
|
+
seoKeywords: 'keyword1, keyword2, keyword3'
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const result = createPostSchema.safeParse(data)
|
|
427
|
+
|
|
428
|
+
expect(result.success).toBe(true)
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
it('should reject seoTitle longer than 255 characters', () => {
|
|
432
|
+
const data = {
|
|
433
|
+
slug: 'test',
|
|
434
|
+
title: 'Test',
|
|
435
|
+
seoTitle: 'a'.repeat(256)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
const result = createPostSchema.safeParse(data)
|
|
439
|
+
|
|
440
|
+
expect(result.success).toBe(false)
|
|
441
|
+
})
|
|
442
|
+
|
|
443
|
+
it('should accept missing SEO fields (optional)', () => {
|
|
444
|
+
const data = {
|
|
445
|
+
slug: 'test',
|
|
446
|
+
title: 'Test'
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const result = createPostSchema.safeParse(data)
|
|
450
|
+
|
|
451
|
+
expect(result.success).toBe(true)
|
|
452
|
+
if (result.success) {
|
|
453
|
+
expect(result.data.seoTitle).toBeUndefined()
|
|
454
|
+
expect(result.data.seoDescription).toBeUndefined()
|
|
455
|
+
}
|
|
456
|
+
})
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
describe('createPostSchema - Boolean Flags', () => {
|
|
460
|
+
it('should default noindex to false', () => {
|
|
461
|
+
const data = {
|
|
462
|
+
slug: 'test',
|
|
463
|
+
title: 'Test'
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const result = createPostSchema.safeParse(data)
|
|
467
|
+
|
|
468
|
+
expect(result.success).toBe(true)
|
|
469
|
+
if (result.success) {
|
|
470
|
+
expect(result.data.noindex).toBe(false)
|
|
471
|
+
}
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
it('should accept noindex as true', () => {
|
|
475
|
+
const data = {
|
|
476
|
+
slug: 'test',
|
|
477
|
+
title: 'Test',
|
|
478
|
+
noindex: true
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const result = createPostSchema.safeParse(data)
|
|
482
|
+
|
|
483
|
+
expect(result.success).toBe(true)
|
|
484
|
+
if (result.success) {
|
|
485
|
+
expect(result.data.noindex).toBe(true)
|
|
486
|
+
}
|
|
487
|
+
})
|
|
488
|
+
|
|
489
|
+
it('should default published to false', () => {
|
|
490
|
+
const data = {
|
|
491
|
+
slug: 'test',
|
|
492
|
+
title: 'Test'
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const result = createPostSchema.safeParse(data)
|
|
496
|
+
|
|
497
|
+
expect(result.success).toBe(true)
|
|
498
|
+
if (result.success) {
|
|
499
|
+
expect(result.data.published).toBe(false)
|
|
500
|
+
}
|
|
501
|
+
})
|
|
502
|
+
})
|
|
503
|
+
|
|
504
|
+
describe('createPostSchema - Category IDs', () => {
|
|
505
|
+
it('should accept array of category IDs', () => {
|
|
506
|
+
const data = {
|
|
507
|
+
slug: 'test',
|
|
508
|
+
title: 'Test',
|
|
509
|
+
categoryIds: ['cat-1', 'cat-2', 'cat-3']
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const result = createPostSchema.safeParse(data)
|
|
513
|
+
|
|
514
|
+
expect(result.success).toBe(true)
|
|
515
|
+
if (result.success) {
|
|
516
|
+
expect(result.data.categoryIds).toHaveLength(3)
|
|
517
|
+
}
|
|
518
|
+
})
|
|
519
|
+
|
|
520
|
+
it('should accept empty category IDs array', () => {
|
|
521
|
+
const data = {
|
|
522
|
+
slug: 'test',
|
|
523
|
+
title: 'Test',
|
|
524
|
+
categoryIds: []
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const result = createPostSchema.safeParse(data)
|
|
528
|
+
|
|
529
|
+
expect(result.success).toBe(true)
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
it('should accept missing categoryIds (optional)', () => {
|
|
533
|
+
const data = {
|
|
534
|
+
slug: 'test',
|
|
535
|
+
title: 'Test'
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const result = createPostSchema.safeParse(data)
|
|
539
|
+
|
|
540
|
+
expect(result.success).toBe(true)
|
|
541
|
+
if (result.success) {
|
|
542
|
+
expect(result.data.categoryIds).toBeUndefined()
|
|
543
|
+
}
|
|
544
|
+
})
|
|
545
|
+
})
|
|
546
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../../../tsconfig.cypress.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"baseUrl": "../../../..",
|
|
5
|
+
"paths": {
|
|
6
|
+
"@/core/*": ["./packages/core/*"],
|
|
7
|
+
"@/contents/*": ["./contents/*"],
|
|
8
|
+
"@/*": ["./*"]
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"include": [
|
|
12
|
+
"../../../../cypress.d.ts",
|
|
13
|
+
"cypress/**/*.ts"
|
|
14
|
+
]
|
|
15
|
+
}
|