@mintlify/common 1.0.1023 → 1.0.1025
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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/navigation/decoratedNavigationConfig.d.ts +2 -0
- package/dist/navigation/decoratedNavigationConfig.js +204 -0
- package/dist/navigation/filterDivisions.d.ts +65 -0
- package/dist/navigation/filterDivisions.js +697 -0
- package/dist/navigation/filterDivisions.test.d.ts +1 -0
- package/dist/navigation/filterDivisions.test.js +670 -0
- package/dist/navigation/getScopedNavForPath.test.d.ts +1 -0
- package/dist/navigation/getScopedNavForPath.test.js +135 -0
- package/dist/navigation/getVersionOrLanguageFromPath.d.ts +12 -0
- package/dist/navigation/getVersionOrLanguageFromPath.js +71 -0
- package/dist/navigation/getVersionOrLanguageFromPath.test.d.ts +1 -0
- package/dist/navigation/getVersionOrLanguageFromPath.test.js +133 -0
- package/dist/navigation/index.d.ts +5 -0
- package/dist/navigation/index.js +5 -0
- package/dist/navigation/isCommonPage.d.ts +8 -0
- package/dist/navigation/isCommonPage.js +36 -0
- package/dist/navigation/isCommonPage.test.d.ts +1 -0
- package/dist/navigation/isCommonPage.test.js +56 -0
- package/dist/navigation/pathsDifferByOneSegment.test.d.ts +1 -0
- package/dist/navigation/pathsDifferByOneSegment.test.js +91 -0
- package/dist/navigation/scopeNavStateFlip.test.d.ts +1 -0
- package/dist/navigation/scopeNavStateFlip.test.js +567 -0
- package/dist/navigation/scopeNavToPath.d.ts +18 -0
- package/dist/navigation/scopeNavToPath.js +502 -0
- package/dist/navigation/scopeNavToPath.test.d.ts +1 -0
- package/dist/navigation/scopeNavToPath.test.js +117 -0
- package/dist/navigation/scopeOffPathDivisions.test.d.ts +1 -0
- package/dist/navigation/scopeOffPathDivisions.test.js +852 -0
- package/dist/navigation/scopeProductNav.test.d.ts +1 -0
- package/dist/navigation/scopeProductNav.test.js +288 -0
- package/dist/navigation/updateFirstHrefInDivision.d.ts +30 -0
- package/dist/navigation/updateFirstHrefInDivision.js +388 -0
- package/dist/navigation/updateFirstHrefInDivision.test.d.ts +1 -0
- package/dist/navigation/updateFirstHrefInDivision.test.js +52 -0
- package/dist/paths/isEqualIgnoringLeadingSlash.d.ts +1 -0
- package/dist/paths/isEqualIgnoringLeadingSlash.js +21 -0
- package/dist/paths/isEqualIgnoringLeadingSlash.test.d.ts +1 -0
- package/dist/paths/isEqualIgnoringLeadingSlash.test.js +43 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
import { isEqualIgnoringLeadingSlash } from '../paths/isEqualIgnoringLeadingSlash.js';
|
|
2
|
+
import { decoratedNavigationConfig } from './decoratedNavigationConfig.js';
|
|
3
|
+
import { filterDivisions } from './filterDivisions.js';
|
|
4
|
+
import { getAllPathsInDecoratedNav } from './getAllPathsInDecoratedNav.js';
|
|
5
|
+
import { getScopedNavForPath } from './scopeNavToPath.js';
|
|
6
|
+
function filterWith(decoratedNav, currentPath, currentVersion, currentLanguage, currentProduct) {
|
|
7
|
+
return filterDivisions({
|
|
8
|
+
currentPath,
|
|
9
|
+
decoratedNav,
|
|
10
|
+
userGroups: new Set(),
|
|
11
|
+
currentVersion,
|
|
12
|
+
currentLanguage,
|
|
13
|
+
currentProduct,
|
|
14
|
+
shouldUseDivisionMatch: true,
|
|
15
|
+
cache: { dropdownCache: new Map() },
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function divisionSummary(result) {
|
|
19
|
+
return {
|
|
20
|
+
tabs: result.tabs.map(({ name, isActive }) => ({ name, isActive })),
|
|
21
|
+
anchors: result.anchors.map(({ name, isActive }) => ({ name, isActive })),
|
|
22
|
+
versions: result.versions.map(({ name, isActive }) => ({ name, isActive })),
|
|
23
|
+
languages: result.languages.map(({ name, isActive }) => ({ name, isActive })),
|
|
24
|
+
dropdowns: result.dropdowns.map(({ name, isActive }) => ({ name, isActive })),
|
|
25
|
+
products: result.products.map(({ name, isActive }) => ({ name, isActive })),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The correctness property behind same-url division switches: for every path
|
|
30
|
+
* and every selectable version/language, EITHER the precomputed jump target
|
|
31
|
+
* navigates away (the destination's own payload takes over) OR the switch
|
|
32
|
+
* keeps the url — in which case the client re-renders the scoped tree with
|
|
33
|
+
* flipped session state, and that render must match the full nav exactly.
|
|
34
|
+
*/
|
|
35
|
+
function assertStateFlipSafety(nav) {
|
|
36
|
+
const paths = getAllPathsInDecoratedNav(nav);
|
|
37
|
+
expect(paths.length).toBeGreaterThan(0);
|
|
38
|
+
for (const path of paths) {
|
|
39
|
+
const currentPath = `/${path.replace(/^\//, '')}`;
|
|
40
|
+
const scoped = getScopedNavForPath({ decoratedNav: nav, currentPath });
|
|
41
|
+
const full = filterWith(nav, currentPath, scoped.currentVersion, scoped.currentLanguage);
|
|
42
|
+
const rescoped = filterWith(scoped.scopedNav, currentPath, scoped.currentVersion, scoped.currentLanguage);
|
|
43
|
+
// the picker only offers divisions present in the chrome, so parity is
|
|
44
|
+
// asserted for those; full-nav maps can carry unreachable entries from
|
|
45
|
+
// other products' divisions that stubs rightly no longer contribute
|
|
46
|
+
const selectableVersions = new Set(full.versions.map(({ name }) => name));
|
|
47
|
+
const selectableLanguages = new Set(full.languages.map(({ language }) => language));
|
|
48
|
+
const selectable = (map, keys) => new Map([...map.entries()].filter(([key]) => keys.has(key)).map(([k, v]) => [k, v.href]));
|
|
49
|
+
expect(selectable(rescoped.firstHrefInVersion, selectableVersions), currentPath).toEqual(selectable(full.firstHrefInVersion, selectableVersions));
|
|
50
|
+
expect(selectable(rescoped.firstHrefInLanguage, selectableLanguages), currentPath).toEqual(selectable(full.firstHrefInLanguage, selectableLanguages));
|
|
51
|
+
for (const version of full.versions) {
|
|
52
|
+
const target = full.firstHrefInVersion.get(version.name);
|
|
53
|
+
const stays = target !== undefined && isEqualIgnoringLeadingSlash(target.href, currentPath);
|
|
54
|
+
if (!stays)
|
|
55
|
+
continue;
|
|
56
|
+
const label = `${currentPath} -> version ${version.name}`;
|
|
57
|
+
const flippedFull = filterWith(nav, currentPath, version.name, scoped.currentLanguage);
|
|
58
|
+
const flippedScoped = filterWith(scoped.scopedNav, currentPath, version.name, scoped.currentLanguage);
|
|
59
|
+
expect(flippedScoped.groupsOrPages, label).toEqual(flippedFull.groupsOrPages);
|
|
60
|
+
expect(divisionSummary(flippedScoped), label).toEqual(divisionSummary(flippedFull));
|
|
61
|
+
}
|
|
62
|
+
// deliberately unguarded (no stays check): even a flip to a stubbed,
|
|
63
|
+
// non-containing product must render identically, because the sidebar is
|
|
64
|
+
// path-driven and the selector chrome survives in product stubs
|
|
65
|
+
for (const product of full.products) {
|
|
66
|
+
const label = `${currentPath} -> product ${product.name}`;
|
|
67
|
+
const flippedFull = filterWith(nav, currentPath, scoped.currentVersion, scoped.currentLanguage, product.name);
|
|
68
|
+
const flippedScoped = filterWith(scoped.scopedNav, currentPath, scoped.currentVersion, scoped.currentLanguage, product.name);
|
|
69
|
+
expect(flippedScoped.groupsOrPages, label).toEqual(flippedFull.groupsOrPages);
|
|
70
|
+
expect(divisionSummary(flippedScoped), label).toEqual(divisionSummary(flippedFull));
|
|
71
|
+
}
|
|
72
|
+
for (const language of full.languages) {
|
|
73
|
+
const target = full.firstHrefInLanguage.get(language.language);
|
|
74
|
+
const stays = target !== undefined && isEqualIgnoringLeadingSlash(target.href, currentPath);
|
|
75
|
+
if (!stays)
|
|
76
|
+
continue;
|
|
77
|
+
const label = `${currentPath} -> language ${language.language}`;
|
|
78
|
+
const flippedFull = filterWith(nav, currentPath, scoped.currentVersion, language.language);
|
|
79
|
+
const flippedScoped = filterWith(scoped.scopedNav, currentPath, scoped.currentVersion, language.language);
|
|
80
|
+
expect(flippedScoped.groupsOrPages, label).toEqual(flippedFull.groupsOrPages);
|
|
81
|
+
expect(divisionSummary(flippedScoped), label).toEqual(divisionSummary(flippedFull));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function versionWithTabs(version, isDefault, sharedPages) {
|
|
86
|
+
return Object.assign(Object.assign({ version }, (isDefault ? { default: true } : {})), { tabs: [
|
|
87
|
+
{
|
|
88
|
+
tab: 'Docs',
|
|
89
|
+
pages: [
|
|
90
|
+
...sharedPages.map((href) => ({ title: `Shared ${href}`, href })),
|
|
91
|
+
{ title: 'Overview', href: `/docs/${version}/overview` },
|
|
92
|
+
{ title: 'Install', href: `/docs/${version}/install` },
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
tab: 'Reference',
|
|
97
|
+
pages: [
|
|
98
|
+
{ title: 'API', href: `/docs/${version}/reference/api` },
|
|
99
|
+
{ title: 'SQL', href: `/docs/${version}/reference/sql` },
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
] });
|
|
103
|
+
}
|
|
104
|
+
// cockroachlabs shape: products wrap version divisions, the docs home and a
|
|
105
|
+
// molt-style tool tab are duplicated into every version at identical hrefs,
|
|
106
|
+
// and a second product has no versions at all
|
|
107
|
+
const productSharedNav = {
|
|
108
|
+
products: [
|
|
109
|
+
{
|
|
110
|
+
product: 'Database',
|
|
111
|
+
versions: [
|
|
112
|
+
{
|
|
113
|
+
version: 'v26',
|
|
114
|
+
default: true,
|
|
115
|
+
tabs: [
|
|
116
|
+
{
|
|
117
|
+
tab: 'Docs',
|
|
118
|
+
pages: [
|
|
119
|
+
{ title: 'Home', href: '/docs' },
|
|
120
|
+
{ title: 'Overview', href: '/docs/v26/overview' },
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
tab: 'Migrate',
|
|
125
|
+
pages: [
|
|
126
|
+
{ title: 'Migration Overview', href: '/docs/molt/overview' },
|
|
127
|
+
{ title: 'Bulk Load', href: '/docs/molt/bulk-load' },
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
version: 'v25',
|
|
134
|
+
tabs: [
|
|
135
|
+
{
|
|
136
|
+
tab: 'Docs',
|
|
137
|
+
pages: [
|
|
138
|
+
{ title: 'Home', href: '/docs' },
|
|
139
|
+
{ title: 'Overview', href: '/docs/v25/overview' },
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
tab: 'Migrate',
|
|
144
|
+
pages: [
|
|
145
|
+
{ title: 'Migration Overview', href: '/docs/molt/overview' },
|
|
146
|
+
{ title: 'Bulk Load', href: '/docs/molt/bulk-load' },
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
product: 'Cloud',
|
|
155
|
+
tabs: [
|
|
156
|
+
{
|
|
157
|
+
tab: 'Deploy',
|
|
158
|
+
pages: [
|
|
159
|
+
{ title: 'Get Started', href: '/docs/cloud/get-started' },
|
|
160
|
+
{ title: 'Clusters', href: '/docs/cloud/clusters' },
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
};
|
|
167
|
+
// the shared home exists in v26 and v25 but NOT v24: switching to v24 from it
|
|
168
|
+
// must navigate (into v24, per the unversioned-fallback fix) instead of
|
|
169
|
+
// staying on a url v24 cannot render
|
|
170
|
+
const partialSharedNav = {
|
|
171
|
+
versions: [
|
|
172
|
+
versionWithTabs('v26', true, ['/docs']),
|
|
173
|
+
versionWithTabs('v25', false, ['/docs']),
|
|
174
|
+
versionWithTabs('v24', false, []),
|
|
175
|
+
],
|
|
176
|
+
};
|
|
177
|
+
// pages that live OUTSIDE every version division: their sidebar never derives
|
|
178
|
+
// from a version division, so stubs are safe there — pinned by the harness
|
|
179
|
+
const unversionedPagesNav = {
|
|
180
|
+
global: {
|
|
181
|
+
anchors: [{ anchor: 'External', href: 'https://example.com' }],
|
|
182
|
+
},
|
|
183
|
+
tabs: [
|
|
184
|
+
{
|
|
185
|
+
tab: 'Company',
|
|
186
|
+
pages: [
|
|
187
|
+
{ title: 'About', href: '/docs/about' },
|
|
188
|
+
{ title: 'Pricing', href: '/docs/pricing' },
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
tab: 'Product',
|
|
193
|
+
versions: [
|
|
194
|
+
versionWithTabs('v2', true, ['/docs/shared-note']),
|
|
195
|
+
versionWithTabs('v1', false, ['/docs/shared-note']),
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
// a page shared across language divisions at the same href — the language
|
|
201
|
+
// analog of the cockroachlabs home page
|
|
202
|
+
const languageSharedNav = {
|
|
203
|
+
languages: [
|
|
204
|
+
{
|
|
205
|
+
language: 'en',
|
|
206
|
+
groups: [
|
|
207
|
+
{
|
|
208
|
+
group: 'Guides',
|
|
209
|
+
pages: [
|
|
210
|
+
{ title: 'Legal', href: '/docs/legal' },
|
|
211
|
+
{ title: 'Overview', href: '/docs/en/overview' },
|
|
212
|
+
],
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
language: 'es',
|
|
218
|
+
groups: [
|
|
219
|
+
{
|
|
220
|
+
group: 'Guides',
|
|
221
|
+
pages: [
|
|
222
|
+
{ title: 'Legal', href: '/docs/legal' },
|
|
223
|
+
{ title: 'Overview', href: '/docs/es/overview' },
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
};
|
|
230
|
+
// sharing at BOTH nesting levels: the home page exists in every version and,
|
|
231
|
+
// within each version, in every language
|
|
232
|
+
const nestedSharedNav = {
|
|
233
|
+
versions: [
|
|
234
|
+
{
|
|
235
|
+
version: 'v2',
|
|
236
|
+
default: true,
|
|
237
|
+
languages: [
|
|
238
|
+
{
|
|
239
|
+
language: 'en',
|
|
240
|
+
groups: [
|
|
241
|
+
{
|
|
242
|
+
group: 'Guides',
|
|
243
|
+
pages: [
|
|
244
|
+
{ title: 'Home', href: '/docs' },
|
|
245
|
+
{ title: 'Overview', href: '/docs/v2/en/overview' },
|
|
246
|
+
],
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
language: 'es',
|
|
252
|
+
groups: [
|
|
253
|
+
{
|
|
254
|
+
group: 'Guides',
|
|
255
|
+
pages: [
|
|
256
|
+
{ title: 'Home', href: '/docs' },
|
|
257
|
+
{ title: 'Overview', href: '/docs/v2/es/overview' },
|
|
258
|
+
],
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
version: 'v1',
|
|
266
|
+
languages: [
|
|
267
|
+
{
|
|
268
|
+
language: 'en',
|
|
269
|
+
groups: [
|
|
270
|
+
{
|
|
271
|
+
group: 'Guides',
|
|
272
|
+
pages: [
|
|
273
|
+
{ title: 'Home', href: '/docs' },
|
|
274
|
+
{ title: 'Overview', href: '/docs/v1/en/overview' },
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
],
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
language: 'es',
|
|
281
|
+
groups: [
|
|
282
|
+
{
|
|
283
|
+
group: 'Guides',
|
|
284
|
+
pages: [
|
|
285
|
+
{ title: 'Home', href: '/docs' },
|
|
286
|
+
{ title: 'Overview', href: '/docs/v1/es/overview' },
|
|
287
|
+
],
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
],
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
};
|
|
295
|
+
// a tool page listed in BOTH products at the same href, plus product menus
|
|
296
|
+
const ambiguousProductNav = {
|
|
297
|
+
products: [
|
|
298
|
+
{
|
|
299
|
+
product: 'Server',
|
|
300
|
+
menu: [
|
|
301
|
+
{
|
|
302
|
+
item: 'Guides',
|
|
303
|
+
pages: [
|
|
304
|
+
{ title: 'Install', href: '/docs/server/install' },
|
|
305
|
+
{ title: 'Configure', href: '/docs/server/configure' },
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
tabs: [
|
|
310
|
+
{
|
|
311
|
+
tab: 'Docs',
|
|
312
|
+
pages: [
|
|
313
|
+
{ title: 'Shared Tool', href: '/docs/shared-tool' },
|
|
314
|
+
{ title: 'Install', href: '/docs/server/install' },
|
|
315
|
+
{ title: 'Configure', href: '/docs/server/configure' },
|
|
316
|
+
],
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
product: 'Cloud',
|
|
322
|
+
menu: [
|
|
323
|
+
{
|
|
324
|
+
item: 'Guides',
|
|
325
|
+
pages: [
|
|
326
|
+
{ title: 'Sign Up', href: '/docs/cloud/sign-up' },
|
|
327
|
+
{ title: 'Billing', href: '/docs/cloud/billing' },
|
|
328
|
+
],
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
tabs: [
|
|
332
|
+
{
|
|
333
|
+
tab: 'Docs',
|
|
334
|
+
pages: [
|
|
335
|
+
{ title: 'Shared Tool', href: '/docs/shared-tool' },
|
|
336
|
+
{ title: 'Sign Up', href: '/docs/cloud/sign-up' },
|
|
337
|
+
{ title: 'Billing', href: '/docs/cloud/billing' },
|
|
338
|
+
],
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
};
|
|
344
|
+
// pages beside the products array, outside every product
|
|
345
|
+
const outsideProductsNav = {
|
|
346
|
+
tabs: [
|
|
347
|
+
{
|
|
348
|
+
tab: 'Company',
|
|
349
|
+
pages: [
|
|
350
|
+
{ title: 'About', href: '/docs/about' },
|
|
351
|
+
{ title: 'Careers', href: '/docs/careers' },
|
|
352
|
+
],
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
tab: 'Product',
|
|
356
|
+
products: [
|
|
357
|
+
{
|
|
358
|
+
product: 'Server',
|
|
359
|
+
pages: [
|
|
360
|
+
{ title: 'Install', href: '/docs/server/install' },
|
|
361
|
+
{ title: 'Configure', href: '/docs/server/configure' },
|
|
362
|
+
],
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
product: 'Cloud',
|
|
366
|
+
pages: [
|
|
367
|
+
{ title: 'Sign Up', href: '/docs/cloud/sign-up' },
|
|
368
|
+
{ title: 'Billing', href: '/docs/cloud/billing' },
|
|
369
|
+
],
|
|
370
|
+
},
|
|
371
|
+
],
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
};
|
|
375
|
+
function tripleLanguage(product, version, language, shared) {
|
|
376
|
+
return {
|
|
377
|
+
language,
|
|
378
|
+
groups: [
|
|
379
|
+
{
|
|
380
|
+
group: 'Guides',
|
|
381
|
+
pages: [
|
|
382
|
+
...shared.map((href) => ({ title: `Shared ${href}`, href })),
|
|
383
|
+
{ title: 'Overview', href: `/docs/${product}/${version}/${language}/overview` },
|
|
384
|
+
{ title: 'Install', href: `/docs/${product}/${version}/${language}/install` },
|
|
385
|
+
],
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
// all three axes nested: products -> versions -> languages, with the home
|
|
391
|
+
// page shared across every version and language of the first product
|
|
392
|
+
const tripleNestedNav = {
|
|
393
|
+
products: [
|
|
394
|
+
{
|
|
395
|
+
product: 'Server',
|
|
396
|
+
versions: [
|
|
397
|
+
{
|
|
398
|
+
version: 'v2',
|
|
399
|
+
default: true,
|
|
400
|
+
languages: [
|
|
401
|
+
tripleLanguage('server', 'v2', 'en', ['/docs', '/docs/all-shared']),
|
|
402
|
+
tripleLanguage('server', 'v2', 'es', ['/docs', '/docs/all-shared']),
|
|
403
|
+
],
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
version: 'v1',
|
|
407
|
+
languages: [
|
|
408
|
+
tripleLanguage('server', 'v1', 'en', ['/docs', '/docs/all-shared']),
|
|
409
|
+
tripleLanguage('server', 'v1', 'es', ['/docs', '/docs/all-shared']),
|
|
410
|
+
],
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
product: 'Cloud',
|
|
416
|
+
versions: [
|
|
417
|
+
{
|
|
418
|
+
version: 'v2',
|
|
419
|
+
default: true,
|
|
420
|
+
languages: [
|
|
421
|
+
tripleLanguage('cloud', 'v2', 'en', ['/docs/all-shared']),
|
|
422
|
+
tripleLanguage('cloud', 'v2', 'es', ['/docs/all-shared']),
|
|
423
|
+
],
|
|
424
|
+
},
|
|
425
|
+
],
|
|
426
|
+
},
|
|
427
|
+
],
|
|
428
|
+
};
|
|
429
|
+
describe('state-flip safety under scoped nav', () => {
|
|
430
|
+
it('holds for the versioned fixture corpus', () => {
|
|
431
|
+
assertStateFlipSafety(decoratedNavigationConfig);
|
|
432
|
+
});
|
|
433
|
+
it('holds for products wrapping versions with shared home and tool pages', () => {
|
|
434
|
+
assertStateFlipSafety(productSharedNav);
|
|
435
|
+
});
|
|
436
|
+
it('holds when the shared page exists in only some versions', () => {
|
|
437
|
+
assertStateFlipSafety(partialSharedNav);
|
|
438
|
+
});
|
|
439
|
+
it('holds for pages outside every version division', () => {
|
|
440
|
+
assertStateFlipSafety(unversionedPagesNav);
|
|
441
|
+
});
|
|
442
|
+
it('holds for pages shared across language divisions', () => {
|
|
443
|
+
assertStateFlipSafety(languageSharedNav);
|
|
444
|
+
});
|
|
445
|
+
it('holds for a page shared across products', () => {
|
|
446
|
+
assertStateFlipSafety(ambiguousProductNav);
|
|
447
|
+
});
|
|
448
|
+
it('holds for pages outside every product', () => {
|
|
449
|
+
assertStateFlipSafety(outsideProductsNav);
|
|
450
|
+
});
|
|
451
|
+
it('holds for pages shared at both version and language level', () => {
|
|
452
|
+
assertStateFlipSafety(nestedSharedNav);
|
|
453
|
+
});
|
|
454
|
+
it('holds for products wrapping versions wrapping languages', () => {
|
|
455
|
+
assertStateFlipSafety(tripleNestedNav);
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
describe('triple-nested stripping', () => {
|
|
459
|
+
it('strips each axis independently on an owned page', () => {
|
|
460
|
+
const scoped = getScopedNavForPath({
|
|
461
|
+
decoratedNav: tripleNestedNav,
|
|
462
|
+
currentPath: '/docs/server/v2/en/overview',
|
|
463
|
+
});
|
|
464
|
+
const serialized = JSON.stringify(scoped.scopedNav);
|
|
465
|
+
expect(serialized).toContain('/docs/server/v2/en/install');
|
|
466
|
+
expect(serialized).not.toContain('/docs/server/v2/es/install');
|
|
467
|
+
expect(serialized).not.toContain('/docs/server/v1/en/install');
|
|
468
|
+
expect(serialized).not.toContain('/docs/cloud/v2/en/install');
|
|
469
|
+
});
|
|
470
|
+
it('keeps every division on a page shared across products, versions, and languages at once', () => {
|
|
471
|
+
const scoped = getScopedNavForPath({
|
|
472
|
+
decoratedNav: tripleNestedNav,
|
|
473
|
+
currentPath: '/docs/all-shared',
|
|
474
|
+
});
|
|
475
|
+
const serialized = JSON.stringify(scoped.scopedNav);
|
|
476
|
+
for (const href of [
|
|
477
|
+
'/docs/server/v2/en/install',
|
|
478
|
+
'/docs/server/v2/es/install',
|
|
479
|
+
'/docs/server/v1/en/install',
|
|
480
|
+
'/docs/server/v1/es/install',
|
|
481
|
+
'/docs/cloud/v2/en/install',
|
|
482
|
+
'/docs/cloud/v2/es/install',
|
|
483
|
+
]) {
|
|
484
|
+
expect(serialized, href).toContain(href);
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
it('keeps every version-language pair containing the shared home, still stubbing the other product', () => {
|
|
488
|
+
const scoped = getScopedNavForPath({ decoratedNav: tripleNestedNav, currentPath: '/docs' });
|
|
489
|
+
const serialized = JSON.stringify(scoped.scopedNav);
|
|
490
|
+
for (const href of [
|
|
491
|
+
'/docs/server/v2/en/install',
|
|
492
|
+
'/docs/server/v2/es/install',
|
|
493
|
+
'/docs/server/v1/en/install',
|
|
494
|
+
'/docs/server/v1/es/install',
|
|
495
|
+
]) {
|
|
496
|
+
expect(serialized, href).toContain(href);
|
|
497
|
+
}
|
|
498
|
+
expect(serialized).toContain('"product":"Cloud"');
|
|
499
|
+
expect(serialized).not.toContain('/docs/cloud/v2/en/install');
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
describe('shared-page scoping details', () => {
|
|
503
|
+
it('keeps both containing versions through the product wrapper on the shared home', () => {
|
|
504
|
+
const scoped = getScopedNavForPath({ decoratedNav: productSharedNav, currentPath: '/docs' });
|
|
505
|
+
const serialized = JSON.stringify(scoped.scopedNav);
|
|
506
|
+
expect(serialized).toContain('/docs/v26/overview');
|
|
507
|
+
expect(serialized).toContain('/docs/v25/overview');
|
|
508
|
+
expect(serialized).toContain('/docs/cloud/get-started');
|
|
509
|
+
});
|
|
510
|
+
it('stubs the non-containing product down to selector chrome', () => {
|
|
511
|
+
const scoped = getScopedNavForPath({ decoratedNav: productSharedNav, currentPath: '/docs' });
|
|
512
|
+
const serialized = JSON.stringify(scoped.scopedNav);
|
|
513
|
+
expect(serialized).toContain('"product":"Cloud"');
|
|
514
|
+
expect(serialized).toContain('/docs/cloud/get-started');
|
|
515
|
+
expect(serialized).not.toContain('/docs/cloud/clusters');
|
|
516
|
+
});
|
|
517
|
+
it('keeps every product containing an ambiguous page', () => {
|
|
518
|
+
const scoped = getScopedNavForPath({
|
|
519
|
+
decoratedNav: ambiguousProductNav,
|
|
520
|
+
currentPath: '/docs/shared-tool',
|
|
521
|
+
});
|
|
522
|
+
const serialized = JSON.stringify(scoped.scopedNav);
|
|
523
|
+
expect(serialized).toContain('/docs/server/configure');
|
|
524
|
+
expect(serialized).toContain('/docs/cloud/billing');
|
|
525
|
+
});
|
|
526
|
+
it('preserves a stubbed product menu with one resolved page per item', () => {
|
|
527
|
+
const scoped = getScopedNavForPath({
|
|
528
|
+
decoratedNav: ambiguousProductNav,
|
|
529
|
+
currentPath: '/docs/server/install',
|
|
530
|
+
});
|
|
531
|
+
const serialized = JSON.stringify(scoped.scopedNav);
|
|
532
|
+
expect(serialized).toContain('"item":"Guides"');
|
|
533
|
+
expect(serialized).toContain('/docs/cloud/sign-up');
|
|
534
|
+
expect(serialized).not.toContain('/docs/cloud/billing');
|
|
535
|
+
});
|
|
536
|
+
it('stubs no product when the path lives outside all of them', () => {
|
|
537
|
+
const scoped = getScopedNavForPath({
|
|
538
|
+
decoratedNav: outsideProductsNav,
|
|
539
|
+
currentPath: '/docs/about',
|
|
540
|
+
});
|
|
541
|
+
const serialized = JSON.stringify(scoped.scopedNav);
|
|
542
|
+
expect(serialized).toContain('/docs/server/configure');
|
|
543
|
+
expect(serialized).toContain('/docs/cloud/billing');
|
|
544
|
+
});
|
|
545
|
+
it('sends the switch into the target version when the shared page is absent there', () => {
|
|
546
|
+
const scoped = getScopedNavForPath({ decoratedNav: partialSharedNav, currentPath: '/docs' });
|
|
547
|
+
const target = scoped.firstHrefInVersion.get('v24');
|
|
548
|
+
expect(target).toBeDefined();
|
|
549
|
+
expect(target === null || target === void 0 ? void 0 : target.href).not.toBe('/docs');
|
|
550
|
+
expect(target === null || target === void 0 ? void 0 : target.href.startsWith('/docs/v24/')).toBe(true);
|
|
551
|
+
});
|
|
552
|
+
it('stubs the absent version division on the partially shared page', () => {
|
|
553
|
+
const scoped = getScopedNavForPath({ decoratedNav: partialSharedNav, currentPath: '/docs' });
|
|
554
|
+
const serialized = JSON.stringify(scoped.scopedNav);
|
|
555
|
+
expect(serialized).toContain('/docs/v25/overview');
|
|
556
|
+
expect(serialized).not.toContain('/docs/v24/reference/sql');
|
|
557
|
+
});
|
|
558
|
+
it('still stubs everything for a page owned by a single version', () => {
|
|
559
|
+
const scoped = getScopedNavForPath({
|
|
560
|
+
decoratedNav: partialSharedNav,
|
|
561
|
+
currentPath: '/docs/v26/reference/api',
|
|
562
|
+
});
|
|
563
|
+
const serialized = JSON.stringify(scoped.scopedNav);
|
|
564
|
+
expect(serialized).not.toContain('/docs/v25/reference/sql');
|
|
565
|
+
expect(serialized).not.toContain('/docs/v24/reference/sql');
|
|
566
|
+
});
|
|
567
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { LocaleType } from '@mintlify/models';
|
|
2
|
+
import type { DecoratedNavigationConfig, DocsConfig } from '@mintlify/validation';
|
|
3
|
+
import { DivisionMatch } from './updateFirstHrefInDivision.js';
|
|
4
|
+
export declare function scopeDecoratedNavToPath(nav: DecoratedNavigationConfig, currentPath: string): DecoratedNavigationConfig;
|
|
5
|
+
export declare function scopeDocsConfigNavToPath(docsConfig: DocsConfig, currentPath: string): DocsConfig;
|
|
6
|
+
export type ScopedNavForPath = {
|
|
7
|
+
scopedNav: DecoratedNavigationConfig;
|
|
8
|
+
firstHrefInVersion: Map<string | undefined, DivisionMatch>;
|
|
9
|
+
firstHrefInLanguage: Map<LocaleType | undefined, DivisionMatch>;
|
|
10
|
+
currentVersion: string | undefined;
|
|
11
|
+
currentLanguage: LocaleType | undefined;
|
|
12
|
+
};
|
|
13
|
+
export declare function getScopedNavForPath({ decoratedNav, currentPath, userGroups, isPreview, }: {
|
|
14
|
+
decoratedNav: DecoratedNavigationConfig;
|
|
15
|
+
currentPath: string;
|
|
16
|
+
userGroups?: Set<string>;
|
|
17
|
+
isPreview?: boolean;
|
|
18
|
+
}): ScopedNavForPath;
|