@levino/shipyard-docs 0.5.2 → 0.6.0-rc-20260106213612
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/astro/Layout.astro +73 -23
- package/package.json +16 -6
- package/src/fallbacks.test.ts +136 -0
- package/src/fallbacks.ts +82 -0
- package/src/index.ts +177 -50
- package/src/pagination.test.ts +115 -15
- package/src/pagination.ts +44 -16
- package/src/schema.test.ts +467 -0
- package/src/sidebarEntries.test.ts +145 -7
- package/src/sidebarEntries.ts +30 -3
package/src/pagination.ts
CHANGED
|
@@ -78,9 +78,9 @@ export const getPaginationInfo = (
|
|
|
78
78
|
(doc) => normalizePath(doc.path) === normalizedCurrentPath,
|
|
79
79
|
)
|
|
80
80
|
|
|
81
|
-
// Check for explicit pagination overrides in frontmatter
|
|
82
|
-
const paginationNext = currentDoc?.
|
|
83
|
-
const paginationPrev = currentDoc?.
|
|
81
|
+
// Check for explicit pagination overrides in frontmatter (using camelCase field names)
|
|
82
|
+
const paginationNext = currentDoc?.paginationNext
|
|
83
|
+
const paginationPrev = currentDoc?.paginationPrev
|
|
84
84
|
|
|
85
85
|
// If explicitly disabled (null), return empty pagination
|
|
86
86
|
if (paginationNext === null && paginationPrev === null) {
|
|
@@ -88,6 +88,7 @@ export const getPaginationInfo = (
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
// Flatten the sidebar to get ordered list of pages
|
|
91
|
+
// Note: unlisted pages are already excluded from sidebar entries
|
|
91
92
|
const flatPages = flattenSidebarEntries(sidebarEntries)
|
|
92
93
|
|
|
93
94
|
// Find current page index (using normalized paths for comparison)
|
|
@@ -116,16 +117,43 @@ export const getPaginationInfo = (
|
|
|
116
117
|
return {}
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Gets the display title for a doc, using paginationLabel if available,
|
|
122
|
+
* then falling back to sidebarLabel, then title.
|
|
123
|
+
*/
|
|
124
|
+
const getDisplayTitle = (doc: DocsData): string =>
|
|
125
|
+
doc.paginationLabel ?? doc.sidebarLabel ?? doc.title
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Finds a doc by its ID, checking both the content collection ID and custom frontmatter ID.
|
|
129
|
+
* This supports Docusaurus-style custom document IDs for pagination references.
|
|
130
|
+
*/
|
|
131
|
+
const findDocById = (targetId: string): DocsData | undefined =>
|
|
132
|
+
allDocs.find((doc) => doc.id === targetId || doc.customId === targetId)
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Gets the pagination link for a page, using paginationLabel for the title
|
|
136
|
+
* if available in the doc's frontmatter.
|
|
137
|
+
*/
|
|
138
|
+
const getPaginationLink = (page: FlattenedEntry): PaginationLink => {
|
|
139
|
+
const normalizedHref = normalizePath(page.href)
|
|
140
|
+
const doc = allDocs.find((d) => normalizePath(d.path) === normalizedHref)
|
|
141
|
+
return {
|
|
142
|
+
title: doc ? getDisplayTitle(doc) : page.title,
|
|
143
|
+
href: page.href,
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
119
147
|
const result: PaginationInfo = {}
|
|
120
148
|
|
|
121
149
|
// Special handling for index pages: they come before all sidebar items
|
|
122
150
|
if (isIndexPage) {
|
|
123
151
|
// Index page has no previous, and first sidebar item as next
|
|
124
152
|
if (paginationPrev !== null && typeof paginationPrev === 'string') {
|
|
125
|
-
const targetDoc =
|
|
153
|
+
const targetDoc = findDocById(paginationPrev)
|
|
126
154
|
if (targetDoc?.path) {
|
|
127
155
|
result.prev = {
|
|
128
|
-
title: targetDoc
|
|
156
|
+
title: getDisplayTitle(targetDoc),
|
|
129
157
|
href: targetDoc.path,
|
|
130
158
|
}
|
|
131
159
|
}
|
|
@@ -136,16 +164,16 @@ export const getPaginationInfo = (
|
|
|
136
164
|
// Explicitly disabled
|
|
137
165
|
result.next = undefined
|
|
138
166
|
} else if (typeof paginationNext === 'string') {
|
|
139
|
-
const targetDoc =
|
|
167
|
+
const targetDoc = findDocById(paginationNext)
|
|
140
168
|
if (targetDoc?.path) {
|
|
141
169
|
result.next = {
|
|
142
|
-
title: targetDoc
|
|
170
|
+
title: getDisplayTitle(targetDoc),
|
|
143
171
|
href: targetDoc.path,
|
|
144
172
|
}
|
|
145
173
|
}
|
|
146
174
|
} else if (flatPages.length > 0) {
|
|
147
175
|
// Use the first sidebar item as next
|
|
148
|
-
result.next = flatPages[0]
|
|
176
|
+
result.next = getPaginationLink(flatPages[0])
|
|
149
177
|
}
|
|
150
178
|
|
|
151
179
|
return result
|
|
@@ -156,17 +184,17 @@ export const getPaginationInfo = (
|
|
|
156
184
|
// Explicitly disabled
|
|
157
185
|
result.prev = undefined
|
|
158
186
|
} else if (typeof paginationPrev === 'string') {
|
|
159
|
-
// Explicitly set to a specific page ID
|
|
160
|
-
const targetDoc =
|
|
187
|
+
// Explicitly set to a specific page ID (supports custom frontmatter IDs)
|
|
188
|
+
const targetDoc = findDocById(paginationPrev)
|
|
161
189
|
if (targetDoc?.path) {
|
|
162
190
|
result.prev = {
|
|
163
|
-
title: targetDoc
|
|
191
|
+
title: getDisplayTitle(targetDoc),
|
|
164
192
|
href: targetDoc.path,
|
|
165
193
|
}
|
|
166
194
|
}
|
|
167
195
|
} else if (currentIndex > 0) {
|
|
168
196
|
// Use the previous page in sidebar order
|
|
169
|
-
result.prev = flatPages[currentIndex - 1]
|
|
197
|
+
result.prev = getPaginationLink(flatPages[currentIndex - 1])
|
|
170
198
|
}
|
|
171
199
|
|
|
172
200
|
// Handle next page
|
|
@@ -174,17 +202,17 @@ export const getPaginationInfo = (
|
|
|
174
202
|
// Explicitly disabled
|
|
175
203
|
result.next = undefined
|
|
176
204
|
} else if (typeof paginationNext === 'string') {
|
|
177
|
-
// Explicitly set to a specific page ID
|
|
178
|
-
const targetDoc =
|
|
205
|
+
// Explicitly set to a specific page ID (supports custom frontmatter IDs)
|
|
206
|
+
const targetDoc = findDocById(paginationNext)
|
|
179
207
|
if (targetDoc?.path) {
|
|
180
208
|
result.next = {
|
|
181
|
-
title: targetDoc
|
|
209
|
+
title: getDisplayTitle(targetDoc),
|
|
182
210
|
href: targetDoc.path,
|
|
183
211
|
}
|
|
184
212
|
}
|
|
185
213
|
} else if (currentIndex < flatPages.length - 1) {
|
|
186
214
|
// Use the next page in sidebar order
|
|
187
|
-
result.next = flatPages[currentIndex + 1]
|
|
215
|
+
result.next = getPaginationLink(flatPages[currentIndex + 1])
|
|
188
216
|
}
|
|
189
217
|
|
|
190
218
|
return result
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import { docsSchema } from './index'
|
|
3
|
+
|
|
4
|
+
describe('docsSchema', () => {
|
|
5
|
+
it('should accept valid sidebar configuration', () => {
|
|
6
|
+
const validData = {
|
|
7
|
+
sidebar: {
|
|
8
|
+
position: 1,
|
|
9
|
+
label: 'Test Label',
|
|
10
|
+
className: 'custom-class',
|
|
11
|
+
customProps: { badge: 'New' },
|
|
12
|
+
collapsible: true,
|
|
13
|
+
collapsed: false,
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const result = docsSchema.safeParse(validData)
|
|
18
|
+
expect(result.success).toBe(true)
|
|
19
|
+
if (result.success) {
|
|
20
|
+
expect(result.data.sidebar.position).toBe(1)
|
|
21
|
+
expect(result.data.sidebar.label).toBe('Test Label')
|
|
22
|
+
expect(result.data.sidebar.className).toBe('custom-class')
|
|
23
|
+
expect(result.data.sidebar.customProps).toEqual({ badge: 'New' })
|
|
24
|
+
expect(result.data.sidebar.collapsible).toBe(true)
|
|
25
|
+
expect(result.data.sidebar.collapsed).toBe(false)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should reject collapsed: true with collapsible: false', () => {
|
|
30
|
+
const invalidData = {
|
|
31
|
+
sidebar: {
|
|
32
|
+
collapsible: false,
|
|
33
|
+
collapsed: true,
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const result = docsSchema.safeParse(invalidData)
|
|
38
|
+
expect(result.success).toBe(false)
|
|
39
|
+
if (!result.success) {
|
|
40
|
+
expect(result.error.issues[0].message).toBe(
|
|
41
|
+
'sidebar.collapsed cannot be true when sidebar.collapsible is false',
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should accept collapsible: false with collapsed: false', () => {
|
|
47
|
+
const validData = {
|
|
48
|
+
sidebar: {
|
|
49
|
+
collapsible: false,
|
|
50
|
+
collapsed: false,
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const result = docsSchema.safeParse(validData)
|
|
55
|
+
expect(result.success).toBe(true)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('should reject tocMinHeadingLevel > tocMaxHeadingLevel', () => {
|
|
59
|
+
const invalidData = {
|
|
60
|
+
tocMinHeadingLevel: 4,
|
|
61
|
+
tocMaxHeadingLevel: 2,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result = docsSchema.safeParse(invalidData)
|
|
65
|
+
expect(result.success).toBe(false)
|
|
66
|
+
if (!result.success) {
|
|
67
|
+
expect(result.error.issues[0].message).toBe(
|
|
68
|
+
'tocMinHeadingLevel must be <= tocMaxHeadingLevel',
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should accept valid tocMinHeadingLevel <= tocMaxHeadingLevel', () => {
|
|
74
|
+
const validData = {
|
|
75
|
+
tocMinHeadingLevel: 2,
|
|
76
|
+
tocMaxHeadingLevel: 4,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const result = docsSchema.safeParse(validData)
|
|
80
|
+
expect(result.success).toBe(true)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('should apply correct default values for sidebar', () => {
|
|
84
|
+
const emptyData = {}
|
|
85
|
+
|
|
86
|
+
const result = docsSchema.safeParse(emptyData)
|
|
87
|
+
expect(result.success).toBe(true)
|
|
88
|
+
if (result.success) {
|
|
89
|
+
expect(result.data.sidebar.collapsible).toBe(true)
|
|
90
|
+
expect(result.data.sidebar.collapsed).toBe(true)
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('should apply correct default values for page rendering fields', () => {
|
|
95
|
+
const emptyData = {}
|
|
96
|
+
|
|
97
|
+
const result = docsSchema.safeParse(emptyData)
|
|
98
|
+
expect(result.success).toBe(true)
|
|
99
|
+
if (result.success) {
|
|
100
|
+
expect(result.data.render).toBe(true)
|
|
101
|
+
expect(result.data.draft).toBe(false)
|
|
102
|
+
expect(result.data.unlisted).toBe(false)
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('should apply correct default values for layout options', () => {
|
|
107
|
+
const emptyData = {}
|
|
108
|
+
|
|
109
|
+
const result = docsSchema.safeParse(emptyData)
|
|
110
|
+
expect(result.success).toBe(true)
|
|
111
|
+
if (result.success) {
|
|
112
|
+
expect(result.data.hideTitle).toBe(false)
|
|
113
|
+
expect(result.data.hideTableOfContents).toBe(false)
|
|
114
|
+
expect(result.data.hideSidebar).toBe(false)
|
|
115
|
+
expect(result.data.tocMinHeadingLevel).toBe(2)
|
|
116
|
+
expect(result.data.tocMaxHeadingLevel).toBe(3)
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('should accept all page metadata fields', () => {
|
|
121
|
+
const validData = {
|
|
122
|
+
id: 'custom-id',
|
|
123
|
+
title: 'Page Title',
|
|
124
|
+
description: 'Page description',
|
|
125
|
+
keywords: ['keyword1', 'keyword2'],
|
|
126
|
+
image: '/images/og-image.png',
|
|
127
|
+
canonicalUrl: 'https://example.com/page',
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const result = docsSchema.safeParse(validData)
|
|
131
|
+
expect(result.success).toBe(true)
|
|
132
|
+
if (result.success) {
|
|
133
|
+
expect(result.data.id).toBe('custom-id')
|
|
134
|
+
expect(result.data.title).toBe('Page Title')
|
|
135
|
+
expect(result.data.description).toBe('Page description')
|
|
136
|
+
expect(result.data.keywords).toEqual(['keyword1', 'keyword2'])
|
|
137
|
+
expect(result.data.image).toBe('/images/og-image.png')
|
|
138
|
+
expect(result.data.canonicalUrl).toBe('https://example.com/page')
|
|
139
|
+
}
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('should accept page rendering fields', () => {
|
|
143
|
+
const validData = {
|
|
144
|
+
render: false,
|
|
145
|
+
draft: true,
|
|
146
|
+
unlisted: true,
|
|
147
|
+
slug: 'custom-slug',
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const result = docsSchema.safeParse(validData)
|
|
151
|
+
expect(result.success).toBe(true)
|
|
152
|
+
if (result.success) {
|
|
153
|
+
expect(result.data.render).toBe(false)
|
|
154
|
+
expect(result.data.draft).toBe(true)
|
|
155
|
+
expect(result.data.unlisted).toBe(true)
|
|
156
|
+
expect(result.data.slug).toBe('custom-slug')
|
|
157
|
+
}
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
it('should accept pagination fields', () => {
|
|
161
|
+
const validData = {
|
|
162
|
+
paginationLabel: 'Custom Label',
|
|
163
|
+
paginationNext: 'next-page.md',
|
|
164
|
+
paginationPrev: null,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const result = docsSchema.safeParse(validData)
|
|
168
|
+
expect(result.success).toBe(true)
|
|
169
|
+
if (result.success) {
|
|
170
|
+
expect(result.data.paginationLabel).toBe('Custom Label')
|
|
171
|
+
expect(result.data.paginationNext).toBe('next-page.md')
|
|
172
|
+
expect(result.data.paginationPrev).toBeNull()
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('should transform pagination_label snake_case to camelCase', () => {
|
|
177
|
+
const validData = {
|
|
178
|
+
pagination_label: 'Custom Nav Label',
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const result = docsSchema.safeParse(validData)
|
|
182
|
+
expect(result.success).toBe(true)
|
|
183
|
+
if (result.success) {
|
|
184
|
+
expect(result.data.paginationLabel).toBe('Custom Nav Label')
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('should accept git metadata override fields', () => {
|
|
189
|
+
const validData = {
|
|
190
|
+
lastUpdateAuthor: 'John Doe',
|
|
191
|
+
lastUpdateTime: '2024-01-15',
|
|
192
|
+
customEditUrl: 'https://github.com/org/repo/edit/main/docs/page.md',
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const result = docsSchema.safeParse(validData)
|
|
196
|
+
expect(result.success).toBe(true)
|
|
197
|
+
if (result.success) {
|
|
198
|
+
expect(result.data.lastUpdateAuthor).toBe('John Doe')
|
|
199
|
+
expect(result.data.lastUpdateTime).toBeInstanceOf(Date)
|
|
200
|
+
expect(result.data.customEditUrl).toBe(
|
|
201
|
+
'https://github.com/org/repo/edit/main/docs/page.md',
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
it('should accept false for git metadata hide options', () => {
|
|
207
|
+
const validData = {
|
|
208
|
+
lastUpdateAuthor: false,
|
|
209
|
+
lastUpdateTime: false,
|
|
210
|
+
customEditUrl: null,
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const result = docsSchema.safeParse(validData)
|
|
214
|
+
expect(result.success).toBe(true)
|
|
215
|
+
if (result.success) {
|
|
216
|
+
expect(result.data.lastUpdateAuthor).toBe(false)
|
|
217
|
+
expect(result.data.lastUpdateTime).toBe(false)
|
|
218
|
+
expect(result.data.customEditUrl).toBeNull()
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
it('should accept custom meta tags', () => {
|
|
223
|
+
const validData = {
|
|
224
|
+
customMetaTags: [
|
|
225
|
+
{ name: 'author', content: 'John Doe' },
|
|
226
|
+
{ property: 'og:type', content: 'article' },
|
|
227
|
+
{ content: 'some content' },
|
|
228
|
+
],
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const result = docsSchema.safeParse(validData)
|
|
232
|
+
expect(result.success).toBe(true)
|
|
233
|
+
if (result.success) {
|
|
234
|
+
expect(result.data.customMetaTags).toHaveLength(3)
|
|
235
|
+
expect(result.data.customMetaTags?.[0]).toEqual({
|
|
236
|
+
name: 'author',
|
|
237
|
+
content: 'John Doe',
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it('should reject invalid heading level values', () => {
|
|
243
|
+
const invalidMin = { tocMinHeadingLevel: 0 }
|
|
244
|
+
const invalidMax = { tocMaxHeadingLevel: 7 }
|
|
245
|
+
|
|
246
|
+
const resultMin = docsSchema.safeParse(invalidMin)
|
|
247
|
+
const resultMax = docsSchema.safeParse(invalidMax)
|
|
248
|
+
|
|
249
|
+
expect(resultMin.success).toBe(false)
|
|
250
|
+
expect(resultMax.success).toBe(false)
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
it('should accept valid heading level boundary values', () => {
|
|
254
|
+
const validData = {
|
|
255
|
+
tocMinHeadingLevel: 1,
|
|
256
|
+
tocMaxHeadingLevel: 6,
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const result = docsSchema.safeParse(validData)
|
|
260
|
+
expect(result.success).toBe(true)
|
|
261
|
+
if (result.success) {
|
|
262
|
+
expect(result.data.tocMinHeadingLevel).toBe(1)
|
|
263
|
+
expect(result.data.tocMaxHeadingLevel).toBe(6)
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
it('should transform hide_table_of_contents to hideTableOfContents', () => {
|
|
268
|
+
const validData = {
|
|
269
|
+
hide_table_of_contents: true,
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const result = docsSchema.safeParse(validData)
|
|
273
|
+
expect(result.success).toBe(true)
|
|
274
|
+
if (result.success) {
|
|
275
|
+
expect(result.data.hideTableOfContents).toBe(true)
|
|
276
|
+
}
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
it('should transform hide_title to hideTitle', () => {
|
|
280
|
+
const validData = {
|
|
281
|
+
hide_title: true,
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const result = docsSchema.safeParse(validData)
|
|
285
|
+
expect(result.success).toBe(true)
|
|
286
|
+
if (result.success) {
|
|
287
|
+
expect(result.data.hideTitle).toBe(true)
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
it('should transform canonical_url to canonicalUrl', () => {
|
|
292
|
+
const validData = {
|
|
293
|
+
canonical_url: 'https://example.com/canonical',
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const result = docsSchema.safeParse(validData)
|
|
297
|
+
expect(result.success).toBe(true)
|
|
298
|
+
if (result.success) {
|
|
299
|
+
expect(result.data.canonicalUrl).toBe('https://example.com/canonical')
|
|
300
|
+
}
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it('should transform custom_meta_tags to customMetaTags', () => {
|
|
304
|
+
const validData = {
|
|
305
|
+
custom_meta_tags: [
|
|
306
|
+
{ name: 'robots', content: 'noindex, nofollow' },
|
|
307
|
+
{ name: 'author', content: 'Test Author' },
|
|
308
|
+
],
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const result = docsSchema.safeParse(validData)
|
|
312
|
+
expect(result.success).toBe(true)
|
|
313
|
+
if (result.success) {
|
|
314
|
+
expect(result.data.customMetaTags).toHaveLength(2)
|
|
315
|
+
expect(result.data.customMetaTags?.[0]).toEqual({
|
|
316
|
+
name: 'robots',
|
|
317
|
+
content: 'noindex, nofollow',
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
it('should accept sidebar.position in nested sidebar object', () => {
|
|
323
|
+
const validData = {
|
|
324
|
+
sidebar: {
|
|
325
|
+
position: 42,
|
|
326
|
+
},
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const result = docsSchema.safeParse(validData)
|
|
330
|
+
expect(result.success).toBe(true)
|
|
331
|
+
if (result.success) {
|
|
332
|
+
expect(result.data.sidebar.position).toBe(42)
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it('should accept sidebar.label in nested sidebar object', () => {
|
|
337
|
+
const validData = {
|
|
338
|
+
sidebar: {
|
|
339
|
+
label: 'My Custom Label',
|
|
340
|
+
},
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const result = docsSchema.safeParse(validData)
|
|
344
|
+
expect(result.success).toBe(true)
|
|
345
|
+
if (result.success) {
|
|
346
|
+
expect(result.data.sidebar.label).toBe('My Custom Label')
|
|
347
|
+
}
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
it('should accept sidebar.className in nested sidebar object', () => {
|
|
351
|
+
const validData = {
|
|
352
|
+
sidebar: {
|
|
353
|
+
className: 'my-custom-class',
|
|
354
|
+
},
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const result = docsSchema.safeParse(validData)
|
|
358
|
+
expect(result.success).toBe(true)
|
|
359
|
+
if (result.success) {
|
|
360
|
+
expect(result.data.sidebar.className).toBe('my-custom-class')
|
|
361
|
+
}
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
it('should accept sidebar.customProps in nested sidebar object', () => {
|
|
365
|
+
const validData = {
|
|
366
|
+
sidebar: {
|
|
367
|
+
customProps: {
|
|
368
|
+
badge: 'New',
|
|
369
|
+
badgeType: 'success',
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const result = docsSchema.safeParse(validData)
|
|
375
|
+
expect(result.success).toBe(true)
|
|
376
|
+
if (result.success) {
|
|
377
|
+
expect(result.data.sidebar.customProps).toEqual({
|
|
378
|
+
badge: 'New',
|
|
379
|
+
badgeType: 'success',
|
|
380
|
+
})
|
|
381
|
+
}
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
it('should accept full sidebar configuration object', () => {
|
|
385
|
+
const validData = {
|
|
386
|
+
sidebar: {
|
|
387
|
+
position: 10,
|
|
388
|
+
collapsible: false,
|
|
389
|
+
collapsed: false,
|
|
390
|
+
customProps: { featured: true },
|
|
391
|
+
},
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const result = docsSchema.safeParse(validData)
|
|
395
|
+
expect(result.success).toBe(true)
|
|
396
|
+
if (result.success) {
|
|
397
|
+
expect(result.data.sidebar.position).toBe(10)
|
|
398
|
+
expect(result.data.sidebar.collapsible).toBe(false)
|
|
399
|
+
expect(result.data.sidebar.collapsed).toBe(false)
|
|
400
|
+
expect(result.data.sidebar.customProps).toEqual({ featured: true })
|
|
401
|
+
}
|
|
402
|
+
})
|
|
403
|
+
|
|
404
|
+
it('should transform pagination_next snake_case to camelCase', () => {
|
|
405
|
+
const validData = {
|
|
406
|
+
pagination_next: 'my-custom-doc-id',
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const result = docsSchema.safeParse(validData)
|
|
410
|
+
expect(result.success).toBe(true)
|
|
411
|
+
if (result.success) {
|
|
412
|
+
expect(result.data.paginationNext).toBe('my-custom-doc-id')
|
|
413
|
+
}
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
it('should transform pagination_prev snake_case to camelCase', () => {
|
|
417
|
+
const validData = {
|
|
418
|
+
pagination_prev: 'another-doc-id',
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const result = docsSchema.safeParse(validData)
|
|
422
|
+
expect(result.success).toBe(true)
|
|
423
|
+
if (result.success) {
|
|
424
|
+
expect(result.data.paginationPrev).toBe('another-doc-id')
|
|
425
|
+
}
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
it('should handle pagination_prev: null to disable pagination', () => {
|
|
429
|
+
const validData = {
|
|
430
|
+
pagination_prev: null,
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const result = docsSchema.safeParse(validData)
|
|
434
|
+
expect(result.success).toBe(true)
|
|
435
|
+
if (result.success) {
|
|
436
|
+
expect(result.data.paginationPrev).toBeNull()
|
|
437
|
+
}
|
|
438
|
+
})
|
|
439
|
+
|
|
440
|
+
it('should transform pagination_next and pagination_prev together', () => {
|
|
441
|
+
const validData = {
|
|
442
|
+
pagination_next: 'next-doc',
|
|
443
|
+
pagination_prev: null,
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const result = docsSchema.safeParse(validData)
|
|
447
|
+
expect(result.success).toBe(true)
|
|
448
|
+
if (result.success) {
|
|
449
|
+
expect(result.data.paginationNext).toBe('next-doc')
|
|
450
|
+
expect(result.data.paginationPrev).toBeNull()
|
|
451
|
+
}
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
it('should accept title_meta for SEO title override', () => {
|
|
455
|
+
const validData = {
|
|
456
|
+
title: 'Display Title',
|
|
457
|
+
title_meta: 'SEO Optimized Title | Site Name',
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const result = docsSchema.safeParse(validData)
|
|
461
|
+
expect(result.success).toBe(true)
|
|
462
|
+
if (result.success) {
|
|
463
|
+
expect(result.data.title).toBe('Display Title')
|
|
464
|
+
expect(result.data.title_meta).toBe('SEO Optimized Title | Site Name')
|
|
465
|
+
}
|
|
466
|
+
})
|
|
467
|
+
})
|