@fern-api/fdr-sdk 1.2.44-ef8582fd87 → 1.2.45-3a874ebe1e
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/dist/api-definition/migrators/v1ToV2.d.ts.map +1 -1
- package/dist/api-definition/migrators/v1ToV2.js +1 -0
- package/dist/api-definition/migrators/v1ToV2.js.map +1 -1
- package/dist/converters/db/convertAPIDefinitionToDb.d.ts.map +1 -1
- package/dist/converters/db/convertAPIDefinitionToDb.js +1 -0
- package/dist/converters/db/convertAPIDefinitionToDb.js.map +1 -1
- package/dist/converters/read/convertDbAPIDefinitionToRead.d.ts +1 -0
- package/dist/converters/read/convertDbAPIDefinitionToRead.d.ts.map +1 -1
- package/dist/converters/read/convertDbAPIDefinitionToRead.js +1 -0
- package/dist/converters/read/convertDbAPIDefinitionToRead.js.map +1 -1
- package/dist/js/api-definition/index.js +3 -0
- package/dist/js/api-definition/index.js.map +1 -1
- package/dist/js/api-definition/index.mjs +3 -0
- package/dist/js/api-definition/index.mjs.map +1 -1
- package/dist/js/client/FdrClient.js +3 -0
- package/dist/js/client/FdrClient.js.map +1 -1
- package/dist/js/client/FdrClient.mjs +3 -0
- package/dist/js/client/FdrClient.mjs.map +1 -1
- package/dist/js/client/types.js +4 -0
- package/dist/js/client/types.js.map +1 -1
- package/dist/js/client/types.mjs +4 -0
- package/dist/js/client/types.mjs.map +1 -1
- package/dist/js/converters/index.js +16 -2
- package/dist/js/converters/index.js.map +1 -1
- package/dist/js/converters/index.mjs +16 -2
- package/dist/js/converters/index.mjs.map +1 -1
- package/dist/js/index.js +16 -2
- package/dist/js/index.js.map +1 -1
- package/dist/js/index.mjs +16 -2
- package/dist/js/index.mjs.map +1 -1
- package/dist/js/navigation/index.js +14 -2
- package/dist/js/navigation/index.js.map +1 -1
- package/dist/js/navigation/index.mjs +14 -2
- package/dist/js/navigation/index.mjs.map +1 -1
- package/dist/js/navigation/ledger-root-builder.js +27 -8
- package/dist/js/navigation/ledger-root-builder.js.map +1 -1
- package/dist/js/navigation/ledger-root-builder.mjs +27 -8
- package/dist/js/navigation/ledger-root-builder.mjs.map +1 -1
- package/dist/js/orpc-client.js +4 -0
- package/dist/js/orpc-client.js.map +1 -1
- package/dist/js/orpc-client.mjs +4 -0
- package/dist/js/orpc-client.mjs.map +1 -1
- package/dist/navigation/__test__/ledger-root-builder.contentfulRedirectRepro.test.d.ts +2 -0
- package/dist/navigation/__test__/ledger-root-builder.contentfulRedirectRepro.test.d.ts.map +1 -0
- package/dist/navigation/__test__/ledger-root-builder.contentfulRedirectRepro.test.js +308 -0
- package/dist/navigation/__test__/ledger-root-builder.contentfulRedirectRepro.test.js.map +1 -0
- package/dist/navigation/__test__/ledger-root-builder.landingPageRedirect.test.d.ts +2 -0
- package/dist/navigation/__test__/ledger-root-builder.landingPageRedirect.test.d.ts.map +1 -0
- package/dist/navigation/__test__/ledger-root-builder.landingPageRedirect.test.js +227 -0
- package/dist/navigation/__test__/ledger-root-builder.landingPageRedirect.test.js.map +1 -0
- package/dist/navigation/__test__/ledger-root-builder.pointsTo.test.js +122 -10
- package/dist/navigation/__test__/ledger-root-builder.pointsTo.test.js.map +1 -1
- package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.js +317 -0
- package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.js.map +1 -1
- package/dist/navigation/__test__/ledger-root-builder.unversionedPointsTo.test.js +59 -1
- package/dist/navigation/__test__/ledger-root-builder.unversionedPointsTo.test.js.map +1 -1
- package/dist/navigation/ledger-root-builder.d.ts.map +1 -1
- package/dist/navigation/ledger-root-builder.js +42 -11
- package/dist/navigation/ledger-root-builder.js.map +1 -1
- package/dist/navigation/utils/toApis.d.ts +1 -0
- package/dist/navigation/utils/toApis.d.ts.map +1 -1
- package/dist/navigation/utils/updatePointsTo.d.ts.map +1 -1
- package/dist/navigation/utils/updatePointsTo.js +12 -4
- package/dist/navigation/utils/updatePointsTo.js.map +1 -1
- package/dist/orpc-client/api/contract-db.d.ts +3 -0
- package/dist/orpc-client/api/contract-db.d.ts.map +1 -1
- package/dist/orpc-client/api/contract-db.js +1 -0
- package/dist/orpc-client/api/contract-db.js.map +1 -1
- package/dist/orpc-client/api/contract-latest.d.ts +6 -0
- package/dist/orpc-client/api/contract-latest.d.ts.map +1 -1
- package/dist/orpc-client/api/contract-latest.js +1 -0
- package/dist/orpc-client/api/contract-latest.js.map +1 -1
- package/dist/orpc-client/api/contract-read.d.ts +3 -0
- package/dist/orpc-client/api/contract-read.d.ts.map +1 -1
- package/dist/orpc-client/api/contract-read.js +1 -0
- package/dist/orpc-client/api/contract-read.js.map +1 -1
- package/dist/orpc-client/api/contract-register.d.ts +3 -0
- package/dist/orpc-client/api/contract-register.d.ts.map +1 -1
- package/dist/orpc-client/api/contract-register.js +1 -0
- package/dist/orpc-client/api/contract-register.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/api-definition/migrators/v1ToV2.d.ts.map +1 -1
- package/dist/types/converters/db/convertAPIDefinitionToDb.d.ts.map +1 -1
- package/dist/types/converters/read/convertDbAPIDefinitionToRead.d.ts +1 -0
- package/dist/types/converters/read/convertDbAPIDefinitionToRead.d.ts.map +1 -1
- package/dist/types/navigation/__test__/ledger-root-builder.contentfulRedirectRepro.test.d.ts +2 -0
- package/dist/types/navigation/__test__/ledger-root-builder.contentfulRedirectRepro.test.d.ts.map +1 -0
- package/dist/types/navigation/__test__/ledger-root-builder.landingPageRedirect.test.d.ts +2 -0
- package/dist/types/navigation/__test__/ledger-root-builder.landingPageRedirect.test.d.ts.map +1 -0
- package/dist/types/navigation/ledger-root-builder.d.ts.map +1 -1
- package/dist/types/navigation/utils/toApis.d.ts +1 -0
- package/dist/types/navigation/utils/toApis.d.ts.map +1 -1
- package/dist/types/navigation/utils/updatePointsTo.d.ts.map +1 -1
- package/dist/types/orpc-client/api/contract-db.d.ts +3 -0
- package/dist/types/orpc-client/api/contract-db.d.ts.map +1 -1
- package/dist/types/orpc-client/api/contract-latest.d.ts +6 -0
- package/dist/types/orpc-client/api/contract-latest.d.ts.map +1 -1
- package/dist/types/orpc-client/api/contract-read.d.ts +3 -0
- package/dist/types/orpc-client/api/contract-read.d.ts.map +1 -1
- package/dist/types/orpc-client/api/contract-register.d.ts +3 -0
- package/dist/types/orpc-client/api/contract-register.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger-root-builder.contentfulRedirectRepro.test.d.ts","sourceRoot":"","sources":["../../../src/navigation/__test__/ledger-root-builder.contentfulRedirectRepro.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { FernNavigation } from "../../index.js";
|
|
3
|
+
import { buildFullRootFromSegments } from "../ledger-root-builder.js";
|
|
4
|
+
/**
|
|
5
|
+
* Reproduction test for the contentful extensibility redirect regression.
|
|
6
|
+
*
|
|
7
|
+
* The extensibility product has this V1 sidebar structure:
|
|
8
|
+
* SidebarGroup:
|
|
9
|
+
* [0] Section "App Framework" (collapsed: true) -> contains "Overview" page
|
|
10
|
+
* [1] Section "Field Editors" (collapsed: true) -> contains pages
|
|
11
|
+
* [2] Page "OAuth"
|
|
12
|
+
* [3] Section "Webhooks" (collapsed: true) -> contains pages
|
|
13
|
+
*
|
|
14
|
+
* V2 (reads V1 tree directly) redirects /extensibility -> first child of
|
|
15
|
+
* first section -> app-framework/overview.
|
|
16
|
+
*
|
|
17
|
+
* Ledger (reconstructs from segments) should produce the same redirect target.
|
|
18
|
+
*
|
|
19
|
+
* ROOT CAUSE: The publisher's hashSegment uses sequential order (not
|
|
20
|
+
* childPosition) for displaySortOrder in the hash. When childPosition was
|
|
21
|
+
* added, the tabRoot segment hash didn't change, so old nav entries with
|
|
22
|
+
* display_sort_order=0 persisted (INSERT ON CONFLICT DO NOTHING). The reader
|
|
23
|
+
* then sorts OAuth(pos=0) before AppFramework(childIndex=0) due to the seq
|
|
24
|
+
* tiebreaker (tabRoot items have lower seq).
|
|
25
|
+
*
|
|
26
|
+
* FIX: The reader's sort tiebreaker now prefers sections over pages at equal
|
|
27
|
+
* positions. Additionally, the publisher hash now includes childPosition so
|
|
28
|
+
* republishing produces a new segment hash and correct nav entries.
|
|
29
|
+
*/
|
|
30
|
+
function pageRoute(opts) {
|
|
31
|
+
return {
|
|
32
|
+
type: "markdown",
|
|
33
|
+
fullPath: opts.fullPath,
|
|
34
|
+
hidden: false,
|
|
35
|
+
metadata: { title: opts.title, pageId: `pg-${opts.title.toLowerCase().replace(/ /g, "-")}` },
|
|
36
|
+
...(opts.displaySortOrder != null ? { displaySortOrder: opts.displaySortOrder } : {})
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function segment(opts) {
|
|
40
|
+
return {
|
|
41
|
+
segmentId: opts.segmentHash,
|
|
42
|
+
segmentHash: opts.segmentHash,
|
|
43
|
+
section: opts.section,
|
|
44
|
+
locale: "en",
|
|
45
|
+
sortOrder: opts.sortOrder,
|
|
46
|
+
hidden: false,
|
|
47
|
+
metadata: opts.metadata,
|
|
48
|
+
product: opts.product
|
|
49
|
+
? {
|
|
50
|
+
id: opts.product.id,
|
|
51
|
+
displayName: opts.product.displayName,
|
|
52
|
+
icon: null,
|
|
53
|
+
sortOrder: 0,
|
|
54
|
+
hidden: false,
|
|
55
|
+
viewers: [],
|
|
56
|
+
orphaned: false,
|
|
57
|
+
featureFlags: [],
|
|
58
|
+
metadata: { slug: opts.product.slug, default: opts.product.isDefault ?? false }
|
|
59
|
+
}
|
|
60
|
+
: null,
|
|
61
|
+
version: null,
|
|
62
|
+
variant: null,
|
|
63
|
+
tab: null
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const PRODUCT = {
|
|
67
|
+
id: "product:extensibility",
|
|
68
|
+
displayName: "Extensibility",
|
|
69
|
+
slug: "extensibility",
|
|
70
|
+
isDefault: false
|
|
71
|
+
};
|
|
72
|
+
describe("contentful extensibility redirect regression", () => {
|
|
73
|
+
/**
|
|
74
|
+
* Ideal case: sections have childIndex=0,1,3 and OAuth has
|
|
75
|
+
* displaySortOrder=2 (its correct global position).
|
|
76
|
+
* The sort correctly interleaves them.
|
|
77
|
+
*/
|
|
78
|
+
it("correct data: redirects to first section's first page (app-framework/overview)", () => {
|
|
79
|
+
const root = buildFullRootFromSegments({
|
|
80
|
+
basePath: "developers/docs",
|
|
81
|
+
title: "Contentful",
|
|
82
|
+
segments: [
|
|
83
|
+
segment({
|
|
84
|
+
segmentHash: "seg-root",
|
|
85
|
+
section: "",
|
|
86
|
+
sortOrder: 0,
|
|
87
|
+
metadata: { type: "tabRoot" },
|
|
88
|
+
product: PRODUCT
|
|
89
|
+
}),
|
|
90
|
+
segment({
|
|
91
|
+
segmentHash: "seg-app-framework",
|
|
92
|
+
section: "app-framework",
|
|
93
|
+
sortOrder: 1,
|
|
94
|
+
metadata: {
|
|
95
|
+
type: "section",
|
|
96
|
+
title: "App Framework",
|
|
97
|
+
childIndex: 0,
|
|
98
|
+
collapsed: true,
|
|
99
|
+
sidebarDepth: 0
|
|
100
|
+
},
|
|
101
|
+
product: PRODUCT
|
|
102
|
+
}),
|
|
103
|
+
segment({
|
|
104
|
+
segmentHash: "seg-field-editors",
|
|
105
|
+
section: "field-editors",
|
|
106
|
+
sortOrder: 2,
|
|
107
|
+
metadata: {
|
|
108
|
+
type: "section",
|
|
109
|
+
title: "Field Editors",
|
|
110
|
+
childIndex: 1,
|
|
111
|
+
collapsed: true,
|
|
112
|
+
sidebarDepth: 0
|
|
113
|
+
},
|
|
114
|
+
product: PRODUCT
|
|
115
|
+
}),
|
|
116
|
+
segment({
|
|
117
|
+
segmentHash: "seg-webhooks",
|
|
118
|
+
section: "webhooks",
|
|
119
|
+
sortOrder: 3,
|
|
120
|
+
metadata: { type: "section", title: "Webhooks", childIndex: 3, collapsed: true, sidebarDepth: 0 },
|
|
121
|
+
product: PRODUCT
|
|
122
|
+
})
|
|
123
|
+
],
|
|
124
|
+
navBySegment: new Map([
|
|
125
|
+
[
|
|
126
|
+
"seg-root",
|
|
127
|
+
[pageRoute({ fullPath: "extensibility/oauth", title: "OAuth", displaySortOrder: 2 })]
|
|
128
|
+
],
|
|
129
|
+
[
|
|
130
|
+
"seg-app-framework",
|
|
131
|
+
[pageRoute({ fullPath: "extensibility/app-framework/overview", title: "Overview" })]
|
|
132
|
+
],
|
|
133
|
+
[
|
|
134
|
+
"seg-field-editors",
|
|
135
|
+
[
|
|
136
|
+
pageRoute({ fullPath: "extensibility/field-editors/overview", title: "Field Editors Overview" })
|
|
137
|
+
]
|
|
138
|
+
],
|
|
139
|
+
[
|
|
140
|
+
"seg-webhooks",
|
|
141
|
+
[pageRoute({ fullPath: "extensibility/webhooks/overview", title: "Webhooks Overview" })]
|
|
142
|
+
]
|
|
143
|
+
])
|
|
144
|
+
});
|
|
145
|
+
FernNavigation.utils.mutableUpdatePointsTo(root);
|
|
146
|
+
const productGroup = root.child;
|
|
147
|
+
expect(productGroup.type).toBe("productgroup");
|
|
148
|
+
const product = productGroup.children[0];
|
|
149
|
+
expect(product.type).toBe("product");
|
|
150
|
+
expect(product.pointsTo).toBe("developers/docs/extensibility/app-framework/overview");
|
|
151
|
+
});
|
|
152
|
+
/**
|
|
153
|
+
* THE BUG: Sections have childIndex (republished with new publisher code)
|
|
154
|
+
* but the tabRoot segment hash didn't change, so the old nav entry with
|
|
155
|
+
* display_sort_order=0 persists. Sort fires (both have positions), but
|
|
156
|
+
* OAuth(pos=0) ties with AppFramework(childIndex=0). The fix's tiebreaker
|
|
157
|
+
* prefers sections over pages at equal positions.
|
|
158
|
+
*/
|
|
159
|
+
it("stale tabRoot data: sections have childIndex but OAuth stuck at displaySortOrder=0", () => {
|
|
160
|
+
const root = buildFullRootFromSegments({
|
|
161
|
+
basePath: "developers/docs",
|
|
162
|
+
title: "Contentful",
|
|
163
|
+
segments: [
|
|
164
|
+
segment({
|
|
165
|
+
segmentHash: "seg-root",
|
|
166
|
+
section: "",
|
|
167
|
+
sortOrder: 0,
|
|
168
|
+
metadata: { type: "tabRoot" },
|
|
169
|
+
product: PRODUCT
|
|
170
|
+
}),
|
|
171
|
+
segment({
|
|
172
|
+
segmentHash: "seg-app-framework",
|
|
173
|
+
section: "app-framework",
|
|
174
|
+
sortOrder: 1,
|
|
175
|
+
metadata: {
|
|
176
|
+
type: "section",
|
|
177
|
+
title: "App Framework",
|
|
178
|
+
childIndex: 0,
|
|
179
|
+
collapsed: true,
|
|
180
|
+
sidebarDepth: 0
|
|
181
|
+
},
|
|
182
|
+
product: PRODUCT
|
|
183
|
+
}),
|
|
184
|
+
segment({
|
|
185
|
+
segmentHash: "seg-field-editors",
|
|
186
|
+
section: "field-editors",
|
|
187
|
+
sortOrder: 2,
|
|
188
|
+
metadata: {
|
|
189
|
+
type: "section",
|
|
190
|
+
title: "Field Editors",
|
|
191
|
+
childIndex: 1,
|
|
192
|
+
collapsed: true,
|
|
193
|
+
sidebarDepth: 0
|
|
194
|
+
},
|
|
195
|
+
product: PRODUCT
|
|
196
|
+
}),
|
|
197
|
+
segment({
|
|
198
|
+
segmentHash: "seg-webhooks",
|
|
199
|
+
section: "webhooks",
|
|
200
|
+
sortOrder: 3,
|
|
201
|
+
metadata: { type: "section", title: "Webhooks", childIndex: 3, collapsed: true, sidebarDepth: 0 },
|
|
202
|
+
product: PRODUCT
|
|
203
|
+
})
|
|
204
|
+
],
|
|
205
|
+
navBySegment: new Map([
|
|
206
|
+
// STALE: displaySortOrder=0 (sequential index) instead of 2 (childPosition)
|
|
207
|
+
[
|
|
208
|
+
"seg-root",
|
|
209
|
+
[pageRoute({ fullPath: "extensibility/oauth", title: "OAuth", displaySortOrder: 0 })]
|
|
210
|
+
],
|
|
211
|
+
[
|
|
212
|
+
"seg-app-framework",
|
|
213
|
+
[pageRoute({ fullPath: "extensibility/app-framework/overview", title: "Overview" })]
|
|
214
|
+
],
|
|
215
|
+
[
|
|
216
|
+
"seg-field-editors",
|
|
217
|
+
[
|
|
218
|
+
pageRoute({ fullPath: "extensibility/field-editors/overview", title: "Field Editors Overview" })
|
|
219
|
+
]
|
|
220
|
+
],
|
|
221
|
+
[
|
|
222
|
+
"seg-webhooks",
|
|
223
|
+
[pageRoute({ fullPath: "extensibility/webhooks/overview", title: "Webhooks Overview" })]
|
|
224
|
+
]
|
|
225
|
+
])
|
|
226
|
+
});
|
|
227
|
+
FernNavigation.utils.mutableUpdatePointsTo(root);
|
|
228
|
+
const productGroup = root.child;
|
|
229
|
+
expect(productGroup.type).toBe("productgroup");
|
|
230
|
+
const product = productGroup.children[0];
|
|
231
|
+
expect(product.type).toBe("product");
|
|
232
|
+
// FIX: tiebreaker prefers sections over pages at equal positions
|
|
233
|
+
expect(product.pointsTo).toBe("developers/docs/extensibility/app-framework/overview");
|
|
234
|
+
});
|
|
235
|
+
/**
|
|
236
|
+
* Fully old data: sections have NO childIndex at all. The sort condition
|
|
237
|
+
* (hasLeafPos && hasSegmentPos) is not met, so iteration order is used.
|
|
238
|
+
* Without childIndex, the reader can't know the V1 order, so it falls back
|
|
239
|
+
* to segment sortOrder which puts tabRoot first -> OAuth appears first.
|
|
240
|
+
* This case requires a republish with the fixed publisher to resolve.
|
|
241
|
+
*/
|
|
242
|
+
it("fully old data (no childIndex): falls back to iteration order", () => {
|
|
243
|
+
const root = buildFullRootFromSegments({
|
|
244
|
+
basePath: "developers/docs",
|
|
245
|
+
title: "Contentful",
|
|
246
|
+
segments: [
|
|
247
|
+
segment({
|
|
248
|
+
segmentHash: "seg-root",
|
|
249
|
+
section: "",
|
|
250
|
+
sortOrder: 0,
|
|
251
|
+
metadata: { type: "tabRoot" },
|
|
252
|
+
product: PRODUCT
|
|
253
|
+
}),
|
|
254
|
+
segment({
|
|
255
|
+
segmentHash: "seg-app-framework",
|
|
256
|
+
section: "app-framework",
|
|
257
|
+
sortOrder: 1,
|
|
258
|
+
metadata: { type: "section", title: "App Framework", collapsed: true },
|
|
259
|
+
product: PRODUCT
|
|
260
|
+
}),
|
|
261
|
+
segment({
|
|
262
|
+
segmentHash: "seg-field-editors",
|
|
263
|
+
section: "field-editors",
|
|
264
|
+
sortOrder: 2,
|
|
265
|
+
metadata: { type: "section", title: "Field Editors", collapsed: true },
|
|
266
|
+
product: PRODUCT
|
|
267
|
+
}),
|
|
268
|
+
segment({
|
|
269
|
+
segmentHash: "seg-webhooks",
|
|
270
|
+
section: "webhooks",
|
|
271
|
+
sortOrder: 3,
|
|
272
|
+
metadata: { type: "section", title: "Webhooks", collapsed: true },
|
|
273
|
+
product: PRODUCT
|
|
274
|
+
})
|
|
275
|
+
],
|
|
276
|
+
navBySegment: new Map([
|
|
277
|
+
[
|
|
278
|
+
"seg-root",
|
|
279
|
+
[pageRoute({ fullPath: "extensibility/oauth", title: "OAuth", displaySortOrder: 0 })]
|
|
280
|
+
],
|
|
281
|
+
[
|
|
282
|
+
"seg-app-framework",
|
|
283
|
+
[pageRoute({ fullPath: "extensibility/app-framework/overview", title: "Overview" })]
|
|
284
|
+
],
|
|
285
|
+
[
|
|
286
|
+
"seg-field-editors",
|
|
287
|
+
[
|
|
288
|
+
pageRoute({ fullPath: "extensibility/field-editors/overview", title: "Field Editors Overview" })
|
|
289
|
+
]
|
|
290
|
+
],
|
|
291
|
+
[
|
|
292
|
+
"seg-webhooks",
|
|
293
|
+
[pageRoute({ fullPath: "extensibility/webhooks/overview", title: "Webhooks Overview" })]
|
|
294
|
+
]
|
|
295
|
+
])
|
|
296
|
+
});
|
|
297
|
+
FernNavigation.utils.mutableUpdatePointsTo(root);
|
|
298
|
+
const productGroup = root.child;
|
|
299
|
+
expect(productGroup.type).toBe("productgroup");
|
|
300
|
+
const product = productGroup.children[0];
|
|
301
|
+
expect(product.type).toBe("product");
|
|
302
|
+
// Without childIndex, hasSegmentPos=false, so the sort doesn't fire.
|
|
303
|
+
// Iteration order puts OAuth first (tabRoot processed before sections).
|
|
304
|
+
// This is a known limitation — requires republish with fixed publisher.
|
|
305
|
+
expect(product.pointsTo).toBe("developers/docs/extensibility/oauth");
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
//# sourceMappingURL=ledger-root-builder.contentfulRedirectRepro.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger-root-builder.contentfulRedirectRepro.test.js","sourceRoot":"","sources":["../../../src/navigation/__test__/ledger-root-builder.contentfulRedirectRepro.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,SAAS,SAAS,CAAC,IAA2E;IAC1F,OAAO;QACH,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,MAAM,EAAE,KAAK;QACb,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE;QAC5F,GAAG,CAAC,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxF,CAAC;AACN,CAAC;AAED,SAAS,OAAO,CAAC,IAWhB;IACG,OAAO;QACH,SAAS,EAAE,IAAI,CAAC,WAAW;QAC3B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,MAAM,EAAE,IAAI;QACZ,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,MAAM,EAAE,KAAK;QACb,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,OAAO,EAAE,IAAI,CAAC,OAAO;YACjB,CAAC,CAAC;gBACI,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE;gBACnB,WAAW,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW;gBACrC,IAAI,EAAE,IAAI;gBACV,SAAS,EAAE,CAAC;gBACZ,MAAM,EAAE,KAAK;gBACb,OAAO,EAAE,EAAE;gBACX,QAAQ,EAAE,KAAK;gBACf,YAAY,EAAE,EAAE;gBAChB,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,KAAK,EAAE;aAClF;YACH,CAAC,CAAC,IAAI;QACV,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,IAAI;QACb,GAAG,EAAE,IAAI;KACZ,CAAC;AACN,CAAC;AAED,MAAM,OAAO,GAAG;IACZ,EAAE,EAAE,uBAAuB;IAC3B,WAAW,EAAE,eAAe;IAC5B,IAAI,EAAE,eAAe;IACrB,SAAS,EAAE,KAAK;CACnB,CAAC;AAEF,QAAQ,CAAC,8CAA8C,EAAE,GAAG,EAAE;IAC1D;;;;OAIG;IACH,EAAE,CAAC,gFAAgF,EAAE,GAAG,EAAE;QACtF,MAAM,IAAI,GAAG,yBAAyB,CAAC;YACnC,QAAQ,EAAE,iBAAiB;YAC3B,KAAK,EAAE,YAAY;YACnB,QAAQ,EAAE;gBACN,OAAO,CAAC;oBACJ,WAAW,EAAE,UAAU;oBACvB,OAAO,EAAE,EAAE;oBACX,SAAS,EAAE,CAAC;oBACZ,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;oBAC7B,OAAO,EAAE,OAAO;iBACnB,CAAC;gBACF,OAAO,CAAC;oBACJ,WAAW,EAAE,mBAAmB;oBAChC,OAAO,EAAE,eAAe;oBACxB,SAAS,EAAE,CAAC;oBACZ,QAAQ,EAAE;wBACN,IAAI,EAAE,SAAS;wBACf,KAAK,EAAE,eAAe;wBACtB,UAAU,EAAE,CAAC;wBACb,SAAS,EAAE,IAAI;wBACf,YAAY,EAAE,CAAC;qBAClB;oBACD,OAAO,EAAE,OAAO;iBACnB,CAAC;gBACF,OAAO,CAAC;oBACJ,WAAW,EAAE,mBAAmB;oBAChC,OAAO,EAAE,eAAe;oBACxB,SAAS,EAAE,CAAC;oBACZ,QAAQ,EAAE;wBACN,IAAI,EAAE,SAAS;wBACf,KAAK,EAAE,eAAe;wBACtB,UAAU,EAAE,CAAC;wBACb,SAAS,EAAE,IAAI;wBACf,YAAY,EAAE,CAAC;qBAClB;oBACD,OAAO,EAAE,OAAO;iBACnB,CAAC;gBACF,OAAO,CAAC;oBACJ,WAAW,EAAE,cAAc;oBAC3B,OAAO,EAAE,UAAU;oBACnB,SAAS,EAAE,CAAC;oBACZ,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,EAAE;oBACjG,OAAO,EAAE,OAAO;iBACnB,CAAC;aACI;YACV,YAAY,EAAE,IAAI,GAAG,CAAC;gBAClB;oBACI,UAAU;oBACV,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAU;iBACjG;gBACD;oBACI,mBAAmB;oBACnB,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,sCAAsC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAU;iBAChG;gBACD;oBACI,mBAAmB;oBACnB;wBACI,SAAS,CAAC,EAAE,QAAQ,EAAE,sCAAsC,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC;qBAC1F;iBACb;gBACD;oBACI,cAAc;oBACd,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,iCAAiC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAU;iBACpG;aACJ,CAAC;SACL,CAAC,CAAC;QAEH,cAAc,CAAC,KAAK,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAEjD,MAAM,YAAY,GAAG,IAAI,CAAC,KAA+E,CAAC;QAC1G,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC;QAC1C,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH;;;;;;OAMG;IACH,EAAE,CAAC,oFAAoF,EAAE,GAAG,EAAE;QAC1F,MAAM,IAAI,GAAG,yBAAyB,CAAC;YACnC,QAAQ,EAAE,iBAAiB;YAC3B,KAAK,EAAE,YAAY;YACnB,QAAQ,EAAE;gBACN,OAAO,CAAC;oBACJ,WAAW,EAAE,UAAU;oBACvB,OAAO,EAAE,EAAE;oBACX,SAAS,EAAE,CAAC;oBACZ,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;oBAC7B,OAAO,EAAE,OAAO;iBACnB,CAAC;gBACF,OAAO,CAAC;oBACJ,WAAW,EAAE,mBAAmB;oBAChC,OAAO,EAAE,eAAe;oBACxB,SAAS,EAAE,CAAC;oBACZ,QAAQ,EAAE;wBACN,IAAI,EAAE,SAAS;wBACf,KAAK,EAAE,eAAe;wBACtB,UAAU,EAAE,CAAC;wBACb,SAAS,EAAE,IAAI;wBACf,YAAY,EAAE,CAAC;qBAClB;oBACD,OAAO,EAAE,OAAO;iBACnB,CAAC;gBACF,OAAO,CAAC;oBACJ,WAAW,EAAE,mBAAmB;oBAChC,OAAO,EAAE,eAAe;oBACxB,SAAS,EAAE,CAAC;oBACZ,QAAQ,EAAE;wBACN,IAAI,EAAE,SAAS;wBACf,KAAK,EAAE,eAAe;wBACtB,UAAU,EAAE,CAAC;wBACb,SAAS,EAAE,IAAI;wBACf,YAAY,EAAE,CAAC;qBAClB;oBACD,OAAO,EAAE,OAAO;iBACnB,CAAC;gBACF,OAAO,CAAC;oBACJ,WAAW,EAAE,cAAc;oBAC3B,OAAO,EAAE,UAAU;oBACnB,SAAS,EAAE,CAAC;oBACZ,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,EAAE;oBACjG,OAAO,EAAE,OAAO;iBACnB,CAAC;aACI;YACV,YAAY,EAAE,IAAI,GAAG,CAAC;gBAClB,4EAA4E;gBAC5E;oBACI,UAAU;oBACV,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAU;iBACjG;gBACD;oBACI,mBAAmB;oBACnB,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,sCAAsC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAU;iBAChG;gBACD;oBACI,mBAAmB;oBACnB;wBACI,SAAS,CAAC,EAAE,QAAQ,EAAE,sCAAsC,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC;qBAC1F;iBACb;gBACD;oBACI,cAAc;oBACd,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,iCAAiC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAU;iBACpG;aACJ,CAAC;SACL,CAAC,CAAC;QAEH,cAAc,CAAC,KAAK,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAEjD,MAAM,YAAY,GAAG,IAAI,CAAC,KAA+E,CAAC;QAC1G,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC;QAC1C,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrC,iEAAiE;QACjE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,sDAAsD,CAAC,CAAC;IAC1F,CAAC,CAAC,CAAC;IAEH;;;;;;OAMG;IACH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACrE,MAAM,IAAI,GAAG,yBAAyB,CAAC;YACnC,QAAQ,EAAE,iBAAiB;YAC3B,KAAK,EAAE,YAAY;YACnB,QAAQ,EAAE;gBACN,OAAO,CAAC;oBACJ,WAAW,EAAE,UAAU;oBACvB,OAAO,EAAE,EAAE;oBACX,SAAS,EAAE,CAAC;oBACZ,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;oBAC7B,OAAO,EAAE,OAAO;iBACnB,CAAC;gBACF,OAAO,CAAC;oBACJ,WAAW,EAAE,mBAAmB;oBAChC,OAAO,EAAE,eAAe;oBACxB,SAAS,EAAE,CAAC;oBACZ,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,IAAI,EAAE;oBACtE,OAAO,EAAE,OAAO;iBACnB,CAAC;gBACF,OAAO,CAAC;oBACJ,WAAW,EAAE,mBAAmB;oBAChC,OAAO,EAAE,eAAe;oBACxB,SAAS,EAAE,CAAC;oBACZ,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,IAAI,EAAE;oBACtE,OAAO,EAAE,OAAO;iBACnB,CAAC;gBACF,OAAO,CAAC;oBACJ,WAAW,EAAE,cAAc;oBAC3B,OAAO,EAAE,UAAU;oBACnB,SAAS,EAAE,CAAC;oBACZ,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,EAAE;oBACjE,OAAO,EAAE,OAAO;iBACnB,CAAC;aACI;YACV,YAAY,EAAE,IAAI,GAAG,CAAC;gBAClB;oBACI,UAAU;oBACV,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,EAAE,CAAC,CAAU;iBACjG;gBACD;oBACI,mBAAmB;oBACnB,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,sCAAsC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAU;iBAChG;gBACD;oBACI,mBAAmB;oBACnB;wBACI,SAAS,CAAC,EAAE,QAAQ,EAAE,sCAAsC,EAAE,KAAK,EAAE,wBAAwB,EAAE,CAAC;qBAC1F;iBACb;gBACD;oBACI,cAAc;oBACd,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,iCAAiC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC,CAAU;iBACpG;aACJ,CAAC;SACL,CAAC,CAAC;QAEH,cAAc,CAAC,KAAK,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC;QAEjD,MAAM,YAAY,GAAG,IAAI,CAAC,KAA+E,CAAC;QAC1G,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC/C,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC;QAC1C,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACrC,qEAAqE;QACrE,wEAAwE;QACxE,wEAAwE;QACxE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger-root-builder.landingPageRedirect.test.d.ts","sourceRoot":"","sources":["../../../src/navigation/__test__/ledger-root-builder.landingPageRedirect.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import * as FernNavigation from "../index.js";
|
|
3
|
+
import { buildFullRootFromSegments } from "../ledger-root-builder";
|
|
4
|
+
/**
|
|
5
|
+
* Regression test: unversioned sites with tabs + landing page.
|
|
6
|
+
*
|
|
7
|
+
* The V1 (legacy) path computes root.pointsTo via followRedirect(child), which
|
|
8
|
+
* visits unversioned.landingPage before unversioned.child (V1 followRedirect.ts:38):
|
|
9
|
+
*
|
|
10
|
+
* unversioned: (node) => followRedirect(node.landingPage ?? node.child)
|
|
11
|
+
*
|
|
12
|
+
* The V2 (latest) followRedirect does the same via getChildren, which returns
|
|
13
|
+
* [landingPage, child] for unversioned nodes (getChildren.ts:38-39).
|
|
14
|
+
*
|
|
15
|
+
* The ledger builder (buildFullRootFromSegments) bypasses both V1 and the
|
|
16
|
+
* V1→V2 migrator: it builds V2 nodes directly. Before the fix, it seeded
|
|
17
|
+
* root.pointsTo from the first tab's metadata.pointsTo — ignoring the landing
|
|
18
|
+
* page entirely. Then mutableUpdatePointsTo preserved this incorrect value
|
|
19
|
+
* because the target slug existed in the tree.
|
|
20
|
+
*
|
|
21
|
+
* This test reproduces the exact topology of the reported site: an unversioned
|
|
22
|
+
* site with two tabs and a landing page at /welcome. It exercises:
|
|
23
|
+
*
|
|
24
|
+
* 1. The seed value from buildFullRootFromSegments
|
|
25
|
+
* 2. mutableUpdatePointsTo preserving the seed
|
|
26
|
+
* 3. findNode("") returning a redirect to the landing page
|
|
27
|
+
*/
|
|
28
|
+
function tabSegment(opts) {
|
|
29
|
+
return {
|
|
30
|
+
segmentId: opts.segmentHash,
|
|
31
|
+
segmentHash: opts.segmentHash,
|
|
32
|
+
section: opts.section,
|
|
33
|
+
locale: "en",
|
|
34
|
+
sortOrder: opts.sortOrder ?? 0,
|
|
35
|
+
hidden: false,
|
|
36
|
+
metadata: { type: "section", title: opts.section || "Root" },
|
|
37
|
+
product: null,
|
|
38
|
+
version: null,
|
|
39
|
+
variant: null,
|
|
40
|
+
tab: {
|
|
41
|
+
id: `tab:${opts.tabSlug}`,
|
|
42
|
+
displayName: opts.tabSlug,
|
|
43
|
+
icon: null,
|
|
44
|
+
sortOrder: opts.sortOrder ?? 0,
|
|
45
|
+
hidden: false,
|
|
46
|
+
viewers: [],
|
|
47
|
+
orphaned: false,
|
|
48
|
+
featureFlags: [],
|
|
49
|
+
metadata: {
|
|
50
|
+
slug: opts.tabSlug,
|
|
51
|
+
tab_type: "tab",
|
|
52
|
+
...(opts.tabPointsTo != null ? { pointsTo: opts.tabPointsTo } : {})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function markdownRoute(fullPath, title, pageId) {
|
|
58
|
+
return {
|
|
59
|
+
type: "markdown",
|
|
60
|
+
fullPath,
|
|
61
|
+
hidden: false,
|
|
62
|
+
displaySortOrder: 0,
|
|
63
|
+
metadata: { title, pageId }
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Builds a root node mimicking the reported site: unversioned, two tabs
|
|
68
|
+
* ("docs" → /docs/start/about, "api-ref" → /api-ref/functions), plus a
|
|
69
|
+
* landing page at /welcome.
|
|
70
|
+
*/
|
|
71
|
+
function buildTabbedSiteWithLandingPage() {
|
|
72
|
+
return buildFullRootFromSegments({
|
|
73
|
+
basePath: "",
|
|
74
|
+
title: "Tabbed Site With Landing Page",
|
|
75
|
+
segments: [
|
|
76
|
+
{
|
|
77
|
+
segmentId: "seg-landing",
|
|
78
|
+
segmentHash: "seg-landing",
|
|
79
|
+
section: "",
|
|
80
|
+
locale: "en",
|
|
81
|
+
sortOrder: 0,
|
|
82
|
+
hidden: false,
|
|
83
|
+
metadata: { type: "tabRoot", landingPage: true },
|
|
84
|
+
product: null,
|
|
85
|
+
version: null,
|
|
86
|
+
variant: null,
|
|
87
|
+
tab: null
|
|
88
|
+
},
|
|
89
|
+
tabSegment({
|
|
90
|
+
segmentHash: "seg-tab-docs",
|
|
91
|
+
section: "start",
|
|
92
|
+
tabSlug: "docs",
|
|
93
|
+
tabPointsTo: "docs/start/about",
|
|
94
|
+
sortOrder: 1
|
|
95
|
+
}),
|
|
96
|
+
tabSegment({
|
|
97
|
+
segmentHash: "seg-tab-api",
|
|
98
|
+
section: "functions",
|
|
99
|
+
tabSlug: "api-ref",
|
|
100
|
+
tabPointsTo: "api-ref/functions",
|
|
101
|
+
sortOrder: 2
|
|
102
|
+
})
|
|
103
|
+
],
|
|
104
|
+
navBySegment: new Map([
|
|
105
|
+
["seg-landing", [markdownRoute("welcome", "Welcome", "welcome-page")]],
|
|
106
|
+
["seg-tab-docs", [markdownRoute("docs/start/about", "About", "about-page")]],
|
|
107
|
+
["seg-tab-api", [markdownRoute("api-ref/functions", "Functions", "functions-page")]]
|
|
108
|
+
])
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
describe("ledger landing-page redirect regression (ledger vs. legacy parity)", () => {
|
|
112
|
+
it("seeds root.pointsTo to the landing page, not the first tab", () => {
|
|
113
|
+
const root = buildTabbedSiteWithLandingPage();
|
|
114
|
+
expect(root.child.type).toBe("unversioned");
|
|
115
|
+
if (root.child.type !== "unversioned") {
|
|
116
|
+
throw new Error("expected unversioned");
|
|
117
|
+
}
|
|
118
|
+
expect(root.child.landingPage).toBeDefined();
|
|
119
|
+
expect(root.child.landingPage?.slug).toBe("welcome");
|
|
120
|
+
// The ledger builder should seed root.pointsTo to the landing page —
|
|
121
|
+
// matching the V1 legacy behavior (V1 followRedirect visits
|
|
122
|
+
// node.landingPage before node.child).
|
|
123
|
+
expect(root.pointsTo).toBe("welcome");
|
|
124
|
+
// Regression guard: the OLD (broken) seed was the first tab's pointsTo.
|
|
125
|
+
expect(root.pointsTo).not.toBe("docs/start/about");
|
|
126
|
+
});
|
|
127
|
+
it("mutableUpdatePointsTo preserves the landing page as root.pointsTo", () => {
|
|
128
|
+
const root = buildTabbedSiteWithLandingPage();
|
|
129
|
+
// Before update: seed is correct.
|
|
130
|
+
expect(root.pointsTo).toBe("welcome");
|
|
131
|
+
// After update: root.pointsTo should remain "welcome" because the
|
|
132
|
+
// landing page node is present in the slug map.
|
|
133
|
+
FernNavigation.utils.mutableUpdatePointsTo(root);
|
|
134
|
+
expect(root.pointsTo).toBe("welcome");
|
|
135
|
+
});
|
|
136
|
+
it("findNode('') returns redirect to landing page, not first tab page", () => {
|
|
137
|
+
const root = buildTabbedSiteWithLandingPage();
|
|
138
|
+
FernNavigation.utils.mutableUpdatePointsTo(root);
|
|
139
|
+
const result = FernNavigation.utils.findNode(root, FernNavigation.Slug(""));
|
|
140
|
+
// The root URL should redirect to the landing page.
|
|
141
|
+
expect(result.type).toBe("redirect");
|
|
142
|
+
if (result.type === "redirect") {
|
|
143
|
+
expect(result.redirect).toBe("welcome");
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
it("findNode('welcome') resolves the landing page as a found node", () => {
|
|
147
|
+
const root = buildTabbedSiteWithLandingPage();
|
|
148
|
+
FernNavigation.utils.mutableUpdatePointsTo(root);
|
|
149
|
+
const result = FernNavigation.utils.findNode(root, FernNavigation.Slug("welcome"));
|
|
150
|
+
expect(result.type).toBe("found");
|
|
151
|
+
if (result.type === "found") {
|
|
152
|
+
expect(result.node.type).toBe("landingPage");
|
|
153
|
+
expect(result.node.slug).toBe("welcome");
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
it("findNode still resolves tab pages normally", () => {
|
|
157
|
+
const root = buildTabbedSiteWithLandingPage();
|
|
158
|
+
FernNavigation.utils.mutableUpdatePointsTo(root);
|
|
159
|
+
const docs = FernNavigation.utils.findNode(root, FernNavigation.Slug("docs/start/about"));
|
|
160
|
+
expect(docs.type).toBe("found");
|
|
161
|
+
if (docs.type === "found") {
|
|
162
|
+
expect(docs.node.slug).toBe("docs/start/about");
|
|
163
|
+
}
|
|
164
|
+
const api = FernNavigation.utils.findNode(root, FernNavigation.Slug("api-ref/functions"));
|
|
165
|
+
expect(api.type).toBe("found");
|
|
166
|
+
if (api.type === "found") {
|
|
167
|
+
expect(api.node.slug).toBe("api-ref/functions");
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
it("without a landing page, root falls back to first tab's pointsTo", () => {
|
|
171
|
+
const root = buildFullRootFromSegments({
|
|
172
|
+
basePath: "",
|
|
173
|
+
title: "Tabbed Site Without Landing Page",
|
|
174
|
+
segments: [
|
|
175
|
+
tabSegment({
|
|
176
|
+
segmentHash: "seg-tab-docs",
|
|
177
|
+
section: "start",
|
|
178
|
+
tabSlug: "docs",
|
|
179
|
+
tabPointsTo: "docs/start/about",
|
|
180
|
+
sortOrder: 0
|
|
181
|
+
}),
|
|
182
|
+
tabSegment({
|
|
183
|
+
segmentHash: "seg-tab-api",
|
|
184
|
+
section: "functions",
|
|
185
|
+
tabSlug: "api-ref",
|
|
186
|
+
tabPointsTo: "api-ref/functions",
|
|
187
|
+
sortOrder: 1
|
|
188
|
+
})
|
|
189
|
+
],
|
|
190
|
+
navBySegment: new Map([
|
|
191
|
+
["seg-tab-docs", [markdownRoute("docs/start/about", "About", "about-page")]],
|
|
192
|
+
["seg-tab-api", [markdownRoute("api-ref/functions", "Functions", "functions-page")]]
|
|
193
|
+
])
|
|
194
|
+
});
|
|
195
|
+
// No landing page → falls back to first tab's metadata.pointsTo.
|
|
196
|
+
expect(root.pointsTo).toBe("docs/start/about");
|
|
197
|
+
FernNavigation.utils.mutableUpdatePointsTo(root);
|
|
198
|
+
expect(root.pointsTo).toBe("docs/start/about");
|
|
199
|
+
const result = FernNavigation.utils.findNode(root, FernNavigation.Slug(""));
|
|
200
|
+
expect(result.type).toBe("redirect");
|
|
201
|
+
if (result.type === "redirect") {
|
|
202
|
+
expect(result.redirect).toBe("docs/start/about");
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
it("demonstrates the pre-fix bug: manually seeding root.pointsTo to a tab page bypasses the landing page", () => {
|
|
206
|
+
// This test proves the regression existed. If root.pointsTo is
|
|
207
|
+
// incorrectly seeded (as the old code did), the landing page is
|
|
208
|
+
// unreachable from the root URL.
|
|
209
|
+
const root = buildTabbedSiteWithLandingPage();
|
|
210
|
+
// Simulate the OLD (broken) ledger behavior: seed root.pointsTo from
|
|
211
|
+
// the first tab's metadata instead of the landing page.
|
|
212
|
+
root.pointsTo = FernNavigation.Slug("docs/start/about");
|
|
213
|
+
// mutableUpdatePointsTo preserves the broken seed because the slug
|
|
214
|
+
// exists in the tree — it never re-derives from followRedirect.
|
|
215
|
+
FernNavigation.utils.mutableUpdatePointsTo(root);
|
|
216
|
+
expect(root.pointsTo).toBe("docs/start/about");
|
|
217
|
+
// findNode("") follows the broken redirect → wrong page.
|
|
218
|
+
const result = FernNavigation.utils.findNode(root, FernNavigation.Slug(""));
|
|
219
|
+
expect(result.type).toBe("redirect");
|
|
220
|
+
if (result.type === "redirect") {
|
|
221
|
+
expect(result.redirect).toBe("docs/start/about");
|
|
222
|
+
// This is the bug: the user expected "welcome" but got a tab page.
|
|
223
|
+
expect(result.redirect).not.toBe("welcome");
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
//# sourceMappingURL=ledger-root-builder.landingPageRedirect.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger-root-builder.landingPageRedirect.test.js","sourceRoot":"","sources":["../../../src/navigation/__test__/ledger-root-builder.landingPageRedirect.test.ts"],"names":[],"mappings":"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"}
|