@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.
Files changed (42) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +1 -0
  3. package/dist/navigation/decoratedNavigationConfig.d.ts +2 -0
  4. package/dist/navigation/decoratedNavigationConfig.js +204 -0
  5. package/dist/navigation/filterDivisions.d.ts +65 -0
  6. package/dist/navigation/filterDivisions.js +697 -0
  7. package/dist/navigation/filterDivisions.test.d.ts +1 -0
  8. package/dist/navigation/filterDivisions.test.js +670 -0
  9. package/dist/navigation/getScopedNavForPath.test.d.ts +1 -0
  10. package/dist/navigation/getScopedNavForPath.test.js +135 -0
  11. package/dist/navigation/getVersionOrLanguageFromPath.d.ts +12 -0
  12. package/dist/navigation/getVersionOrLanguageFromPath.js +71 -0
  13. package/dist/navigation/getVersionOrLanguageFromPath.test.d.ts +1 -0
  14. package/dist/navigation/getVersionOrLanguageFromPath.test.js +133 -0
  15. package/dist/navigation/index.d.ts +5 -0
  16. package/dist/navigation/index.js +5 -0
  17. package/dist/navigation/isCommonPage.d.ts +8 -0
  18. package/dist/navigation/isCommonPage.js +36 -0
  19. package/dist/navigation/isCommonPage.test.d.ts +1 -0
  20. package/dist/navigation/isCommonPage.test.js +56 -0
  21. package/dist/navigation/pathsDifferByOneSegment.test.d.ts +1 -0
  22. package/dist/navigation/pathsDifferByOneSegment.test.js +91 -0
  23. package/dist/navigation/scopeNavStateFlip.test.d.ts +1 -0
  24. package/dist/navigation/scopeNavStateFlip.test.js +567 -0
  25. package/dist/navigation/scopeNavToPath.d.ts +18 -0
  26. package/dist/navigation/scopeNavToPath.js +502 -0
  27. package/dist/navigation/scopeNavToPath.test.d.ts +1 -0
  28. package/dist/navigation/scopeNavToPath.test.js +117 -0
  29. package/dist/navigation/scopeOffPathDivisions.test.d.ts +1 -0
  30. package/dist/navigation/scopeOffPathDivisions.test.js +852 -0
  31. package/dist/navigation/scopeProductNav.test.d.ts +1 -0
  32. package/dist/navigation/scopeProductNav.test.js +288 -0
  33. package/dist/navigation/updateFirstHrefInDivision.d.ts +30 -0
  34. package/dist/navigation/updateFirstHrefInDivision.js +388 -0
  35. package/dist/navigation/updateFirstHrefInDivision.test.d.ts +1 -0
  36. package/dist/navigation/updateFirstHrefInDivision.test.js +52 -0
  37. package/dist/paths/isEqualIgnoringLeadingSlash.d.ts +1 -0
  38. package/dist/paths/isEqualIgnoringLeadingSlash.js +21 -0
  39. package/dist/paths/isEqualIgnoringLeadingSlash.test.d.ts +1 -0
  40. package/dist/paths/isEqualIgnoringLeadingSlash.test.js +43 -0
  41. package/dist/tsconfig.build.tsbuildinfo +1 -1
  42. 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;