@mintlify/common 1.0.1170 → 1.0.1172

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.
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { Parser } from '@asyncapi/parser';
10
+ import { DiagnosticSeverity, Parser } from '@asyncapi/parser';
11
11
  import { checkForExternalRefs } from './checkForExternalRefs.js';
12
12
  export function validateAsyncApi(str) {
13
13
  return __awaiter(this, void 0, void 0, function* () {
@@ -16,11 +16,18 @@ export function validateAsyncApi(str) {
16
16
  checkForExternalRefs(str);
17
17
  const parser = new Parser();
18
18
  const { document, diagnostics } = yield parser.parse(str);
19
- if (diagnostics.length > 0) {
20
- const errorMessages = diagnostics.map((diagnostic) => diagnostic.message).join('\n');
19
+ const errors = diagnostics.filter((diagnostic) => diagnostic.severity === DiagnosticSeverity.Error);
20
+ if (!document || errors.length > 0) {
21
21
  return {
22
22
  valid: false,
23
- errorMessage: errorMessages,
23
+ errorMessage: errors.map((diagnostic) => diagnostic.message).join('\n'),
24
+ document: undefined,
25
+ };
26
+ }
27
+ if (!document.version().startsWith('3.')) {
28
+ return {
29
+ valid: false,
30
+ errorMessage: `AsyncAPI ${document.version()} is not supported. Please use AsyncAPI 3.0.0.`,
24
31
  document: undefined,
25
32
  };
26
33
  }
@@ -1,3 +1,14 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  import { locales } from '@mintlify/models';
2
13
  import { generatePathToLanguageDict, generatePathToVersionDict } from '../divisions/index.js';
3
14
  import { optionallyRemoveLeadingSlash } from '../fs/optionallySlash.js';
@@ -157,7 +168,10 @@ function divisionValue(division, key) {
157
168
  function resolveStubTarget(division, key, ctx, usePrecomputedTarget) {
158
169
  const targets = key === 'versions' ? ctx.versionTargets : ctx.languageTargets;
159
170
  const value = divisionValue(division, key);
160
- const resolved = usePrecomputedTarget ? targets === null || targets === void 0 ? void 0 : targets.get(value) : undefined;
171
+ const precomputed = usePrecomputedTarget ? targets === null || targets === void 0 ? void 0 : targets.get(value) : undefined;
172
+ const resolved = precomputed && divisionContainsPath(division, precomputed.href) ? precomputed : undefined;
173
+ if (precomputed && !resolved)
174
+ return resolveVisibleTarget(division, ctx);
161
175
  const fallback = pickStubTarget(division, ctx, DIVISION_TYPE_BY_KEY[key]);
162
176
  // the jump-map walk records targets without access checks, so a precomputed
163
177
  // entry only supplies the switch destination; hidden/groups still come from
@@ -204,19 +218,22 @@ function simpleDivisionLabel(division, key) {
204
218
  // client's stateful dropdownCache hrefs, and the active product is client
205
219
  // session state — divisions containing any of them stay whole
206
220
  const STATEFUL_DIVISION_KEYS = new Set(['versions', 'languages', 'dropdowns', 'products']);
207
- function containsStatefulDivision(node) {
221
+ function containsDivision(node, keys) {
208
222
  if (Array.isArray(node))
209
- return node.some(containsStatefulDivision);
223
+ return node.some((entry) => containsDivision(entry, keys));
210
224
  if (!isNavNode(node))
211
225
  return false;
212
226
  for (const [key, value] of Object.entries(node)) {
213
- if (STATEFUL_DIVISION_KEYS.has(key) && Array.isArray(value))
227
+ if (keys.has(key) && Array.isArray(value))
214
228
  return true;
215
- if ((Array.isArray(value) || isNavNode(value)) && containsStatefulDivision(value))
229
+ if ((Array.isArray(value) || isNavNode(value)) && containsDivision(value, keys))
216
230
  return true;
217
231
  }
218
232
  return false;
219
233
  }
234
+ const containsStatefulDivision = (node) => containsDivision(node, STATEFUL_DIVISION_KEYS);
235
+ const PRODUCT_KEYS = new Set(['products']);
236
+ const containsProducts = (node) => containsDivision(node, PRODUCT_KEYS);
220
237
  // mirrors buildDivisionItem's anonymous href resolution (first page in
221
238
  // getFirstPageFromNavigation order that is neither hidden nor group-gated);
222
239
  // falls back to the first raw candidate so a division with no anonymously
@@ -244,6 +261,40 @@ function hasViewerVisiblePage(node, ctx) {
244
261
  node, (_a = ctx.userGroups) !== null && _a !== void 0 ? _a : new Set(), true, ctx.isPreview === true) !== undefined);
245
262
  }
246
263
  const MENU_STUBBABLE_KEYS = new Set(['pages', 'groups']);
264
+ const NODE_LABEL_KEYS = ['version', 'language', 'dropdown', 'tab', 'anchor', 'item'];
265
+ function nodeLabel(node) {
266
+ for (const key of NODE_LABEL_KEYS) {
267
+ if (typeof node[key] === 'string')
268
+ return node[key];
269
+ }
270
+ return '';
271
+ }
272
+ const isContainerKey = (key) => CONTAINER_KEYS.some((containerKey) => containerKey === key);
273
+ function stubPageList(key, entries, label, ctx) {
274
+ const target = resolveVisibleTarget({ [key]: entries }, ctx);
275
+ if (!target)
276
+ return entries;
277
+ const page = target.isRaw ? target.href : makeStubPage(label, target);
278
+ return key === 'pages' ? [page] : [{ group: label, pages: [page] }];
279
+ }
280
+ function skeletonStub(node, ctx) {
281
+ if (typeof node.href === 'string')
282
+ return node;
283
+ const label = nodeLabel(node);
284
+ const stub = {};
285
+ for (const [key, value] of Object.entries(node)) {
286
+ if (Array.isArray(value) && isContainerKey(key)) {
287
+ stub[key] = value.map((entry) => (isNavNode(entry) ? skeletonStub(entry, ctx) : entry));
288
+ }
289
+ else if (Array.isArray(value) && (key === 'pages' || key === 'groups')) {
290
+ stub[key] = stubPageList(key, value, label, ctx);
291
+ }
292
+ else {
293
+ stub[key] = value;
294
+ }
295
+ }
296
+ return stub;
297
+ }
247
298
  // inactive tabs/products still render their full hover menu, so menu items
248
299
  // survive stubbing with their metadata and a single resolved page each
249
300
  function stubMenuItem(item, ctx) {
@@ -252,9 +303,10 @@ function stubMenuItem(item, ctx) {
252
303
  if (typeof item.href === 'string')
253
304
  return item;
254
305
  const subtreeKeys = Object.keys(item).filter((key) => STUB_STRIPPED_KEYS.has(key));
255
- if (!subtreeKeys.length || !subtreeKeys.every((key) => MENU_STUBBABLE_KEYS.has(key))) {
306
+ if (!subtreeKeys.length || containsProducts(item))
256
307
  return scopeNode(item, ctx);
257
- }
308
+ if (subtreeKeys.some((key) => !MENU_STUBBABLE_KEYS.has(key)))
309
+ return skeletonStub(item, ctx);
258
310
  const target = resolveVisibleTarget(item, ctx);
259
311
  if (!target)
260
312
  return item;
@@ -272,7 +324,8 @@ function stubMenuItem(item, ctx) {
272
324
  function stubSimpleDivision(division, key, ctx) {
273
325
  if (typeof division.href === 'string')
274
326
  return division;
275
- if (containsStatefulDivision(division))
327
+ const { menu } = division, rest = __rest(division, ["menu"]);
328
+ if (containsStatefulDivision(rest) || containsProducts(menu))
276
329
  return scopeNode(division, ctx);
277
330
  const target = resolveVisibleTarget(division, ctx);
278
331
  if (!target)
@@ -291,36 +344,30 @@ function stubSimpleDivision(division, key, ctx) {
291
344
  : [makeStubPage(simpleDivisionLabel(division, key), target)];
292
345
  return stub;
293
346
  }
294
- function scopeSimpleDivisionArray(entries, key, ctx) {
295
- const activeIndex = entries.findIndex((entry) => isNavNode(entry) && divisionContainsPath(entry, ctx.currentPath));
347
+ function scopeSiblingArray(entries, ctx, stub) {
348
+ const containsPath = entries.map((entry) => isNavNode(entry) && divisionContainsPath(entry, ctx.currentPath));
296
349
  // when the path-containing division is invisible to the viewer (hidden-only
297
350
  // or group-gated), the client's active-division fallback lands on a sibling
298
351
  // instead — that sibling must keep its real subtree, so nothing is stubbed
299
- const activeEntry = activeIndex === -1 ? undefined : entries[activeIndex];
300
- const activeVisible = isNavNode(activeEntry) && hasViewerVisiblePage(activeEntry, ctx);
352
+ const allContainingVisible = entries.every((entry, index) => !containsPath[index] || (isNavNode(entry) && hasViewerVisiblePage(entry, ctx)));
353
+ const canStub = containsPath.some(Boolean) && allContainingVisible;
301
354
  return entries.map((entry, index) => {
302
355
  if (!isNavNode(entry))
303
356
  return entry;
304
357
  // without a path match the active division is unknowable, so nothing is stubbed
305
- if (!activeVisible || index === activeIndex)
358
+ if (!canStub || containsPath[index])
306
359
  return scopeNode(entry, ctx);
307
- return stubSimpleDivision(entry, key, ctx);
360
+ return stub(entry);
308
361
  });
309
362
  }
363
+ function scopeSimpleDivisionArray(entries, key, ctx) {
364
+ return scopeSiblingArray(entries, ctx, (entry) => stubSimpleDivision(entry, key, ctx));
365
+ }
310
366
  // the same collapse applied to stubbed tabs' menus, but inside the active
311
- // tab/anchor: the item containing the path keeps its subtree (it feeds the
367
+ // tab/anchor: items containing the path keep their subtree (they feed the
312
368
  // sidebar), siblings survive as hover-menu metadata plus one resolved page
313
369
  function scopeMenuArray(entries, ctx) {
314
- const activeIndex = entries.findIndex((entry) => isNavNode(entry) && divisionContainsPath(entry, ctx.currentPath));
315
- const activeEntry = activeIndex === -1 ? undefined : entries[activeIndex];
316
- const activeVisible = isNavNode(activeEntry) && hasViewerVisiblePage(activeEntry, ctx);
317
- return entries.map((entry, index) => {
318
- if (!isNavNode(entry))
319
- return entry;
320
- if (!activeVisible || index === activeIndex)
321
- return scopeNode(entry, ctx);
322
- return stubMenuItem(entry, ctx);
323
- });
370
+ return scopeSiblingArray(entries, ctx, (entry) => stubMenuItem(entry, ctx));
324
371
  }
325
372
  function scopeDivisionArray(entries, key, ctx) {
326
373
  if (ctx.skipVersionLanguageStubs) {
@@ -1,6 +1,7 @@
1
+ import { locales } from '@mintlify/models';
1
2
  import { filterDivisions } from './filterDivisions.js';
2
3
  import { getAllPathsInDecoratedNav } from './getAllPathsInDecoratedNav.js';
3
- import { getScopedNavForPath } from './scopeNavToPath.js';
4
+ import { getScopedNavForPath, scopeDocsConfigNavToPath } from './scopeNavToPath.js';
4
5
  const multiTabNav = {
5
6
  tabs: [
6
7
  {
@@ -459,6 +460,247 @@ const hiddenMenuTabNav = {
459
460
  },
460
461
  ],
461
462
  };
463
+ const autosummary = (base, names) => names.map((name) => ({ title: name, href: `/${base}/_autosummary/${name}` }));
464
+ const productMenuNav = {
465
+ tabs: [
466
+ {
467
+ tab: 'Documentation',
468
+ menu: [
469
+ {
470
+ item: 'Home',
471
+ pages: [{ title: 'Home', href: '/home' }],
472
+ },
473
+ {
474
+ item: 'Engine',
475
+ versions: [
476
+ {
477
+ version: 'v3',
478
+ default: true,
479
+ dropdowns: [
480
+ {
481
+ dropdown: 'Overview',
482
+ groups: [
483
+ {
484
+ group: 'Start',
485
+ pages: [
486
+ { title: 'Engine', href: '/engine/v3/overview' },
487
+ { title: 'Install', href: '/engine/v3/install' },
488
+ ],
489
+ },
490
+ ],
491
+ },
492
+ {
493
+ dropdown: 'Python API',
494
+ groups: [
495
+ {
496
+ group: 'Engine',
497
+ pages: autosummary('engine/v3/py', ['Engine.a', 'Engine.b']),
498
+ },
499
+ {
500
+ group: 'Part',
501
+ pages: autosummary('engine/v3/py', ['Part.a', 'Part.b']),
502
+ },
503
+ ],
504
+ },
505
+ ],
506
+ },
507
+ {
508
+ version: 'v2',
509
+ groups: [
510
+ {
511
+ group: 'Overview',
512
+ pages: [
513
+ { title: 'Engine v2', href: '/engine/v2/overview' },
514
+ ...autosummary('engine/v2', ['Engine.a', 'Engine.b']),
515
+ ],
516
+ },
517
+ ],
518
+ },
519
+ ],
520
+ },
521
+ {
522
+ item: 'Studio',
523
+ versions: [
524
+ {
525
+ version: 'v3',
526
+ default: true,
527
+ dropdowns: [
528
+ {
529
+ dropdown: 'Overview',
530
+ groups: [
531
+ {
532
+ group: 'Start',
533
+ pages: [
534
+ {
535
+ title: 'Draft',
536
+ href: '/studio/v3/draft',
537
+ hidden: true,
538
+ },
539
+ { title: 'Studio', href: '/studio/v3/overview' },
540
+ ],
541
+ },
542
+ ],
543
+ },
544
+ {
545
+ dropdown: 'API',
546
+ groups: [
547
+ {
548
+ group: 'Model',
549
+ pages: [
550
+ {
551
+ title: 'Internal',
552
+ href: '/studio/v3/api/internal',
553
+ groups: ['staff'],
554
+ },
555
+ ...autosummary('studio/v3/api', ['Model.a', 'Model.b']),
556
+ ],
557
+ },
558
+ ],
559
+ },
560
+ ],
561
+ },
562
+ {
563
+ version: 'Legacy',
564
+ hidden: true,
565
+ pages: [{ title: 'Legacy', href: '/studio/legacy/overview' }],
566
+ },
567
+ ],
568
+ },
569
+ {
570
+ item: 'Audio',
571
+ dropdowns: [
572
+ {
573
+ dropdown: 'Overview',
574
+ pages: [{ title: 'Audio', href: '/audio/overview' }],
575
+ },
576
+ {
577
+ dropdown: 'API',
578
+ pages: [
579
+ { title: 'API', href: '/audio/api/overview' },
580
+ ...autosummary('audio/api', ['Clip.a', 'Clip.b']),
581
+ ],
582
+ },
583
+ ],
584
+ },
585
+ {
586
+ item: 'Internal',
587
+ versions: [
588
+ {
589
+ version: 'v3',
590
+ pages: [
591
+ {
592
+ title: 'Runbook',
593
+ href: '/internal/runbook',
594
+ groups: ['staff'],
595
+ },
596
+ ],
597
+ },
598
+ ],
599
+ },
600
+ ],
601
+ },
602
+ {
603
+ tab: 'Knowledge base',
604
+ pages: [{ title: 'Blog', href: '/blog' }],
605
+ },
606
+ ],
607
+ };
608
+ const page = (href) => ({ title: href, href });
609
+ const versionedItem = (item, v3, v2, hidden) => (Object.assign(Object.assign({ item }, (hidden ? { hidden } : {})), { versions: [
610
+ { version: 'v3', default: true, groups: [{ group: `${item} v3`, pages: v3.map(page) }] },
611
+ { version: 'v2', groups: [{ group: `${item} v2`, pages: v2.map(page) }] },
612
+ ] }));
613
+ const crossListedMenuNav = {
614
+ tabs: [
615
+ {
616
+ tab: 'Docs',
617
+ menu: [
618
+ { item: 'Home', pages: [page('/home')] },
619
+ versionedItem('Studio', ['/studio/v3/a', '/studio/v3/b'], ['/studio/v2/a', '/shared']),
620
+ versionedItem('Engine', ['/engine/v3/a', '/shared', '/engine/v3/c'], ['/engine/v2/a']),
621
+ versionedItem('Solver', ['/solver/v3/a'], ['/solver/v2/a']),
622
+ ],
623
+ },
624
+ { tab: 'Other', pages: [page('/other')] },
625
+ ],
626
+ };
627
+ const crossListedHiddenMenuNav = {
628
+ tabs: [
629
+ {
630
+ tab: 'Docs',
631
+ menu: [
632
+ versionedItem('Studio', ['/studio/v3/a'], ['/studio/v2/a', '/shared'], true),
633
+ versionedItem('Engine', ['/engine/v3/a', '/shared'], ['/engine/v2/a']),
634
+ versionedItem('Solver', ['/solver/v3/a'], ['/solver/v2/a']),
635
+ ],
636
+ },
637
+ ],
638
+ };
639
+ const crossListedLanguageMenuNav = {
640
+ tabs: [
641
+ {
642
+ tab: 'Docs',
643
+ menu: ['Studio', 'Engine', 'Solver'].map((item) => ({
644
+ item,
645
+ languages: [
646
+ {
647
+ language: 'en',
648
+ default: true,
649
+ groups: [
650
+ {
651
+ group: `${item} en`,
652
+ pages: [
653
+ page(`/${item.toLowerCase()}/en`),
654
+ ...(item === 'Engine' ? [page('/shared')] : []),
655
+ ],
656
+ },
657
+ ],
658
+ },
659
+ {
660
+ language: 'fr',
661
+ groups: [
662
+ {
663
+ group: `${item} fr`,
664
+ pages: [
665
+ page(`/${item.toLowerCase()}/fr`),
666
+ ...(item === 'Studio' ? [page('/shared')] : []),
667
+ ],
668
+ },
669
+ ],
670
+ },
671
+ ],
672
+ })),
673
+ },
674
+ ],
675
+ };
676
+ const crossListedPlainMenuNav = {
677
+ tabs: [
678
+ {
679
+ tab: 'Docs',
680
+ menu: [
681
+ { item: 'Guides', groups: [{ group: 'Guides', pages: [page('/guides/a'), page('/faq')] }] },
682
+ {
683
+ item: 'Reference',
684
+ groups: [{ group: 'Reference', pages: [page('/ref/a'), page('/faq')] }],
685
+ },
686
+ { item: 'Other', pages: [page('/other/a')] },
687
+ ],
688
+ },
689
+ ],
690
+ };
691
+ const crossListedTabsNav = {
692
+ tabs: [
693
+ {
694
+ tab: 'Studio',
695
+ menu: [versionedItem('Studio', ['/studio/v3/a'], ['/studio/v2/a', '/shared'])],
696
+ },
697
+ {
698
+ tab: 'Engine',
699
+ menu: [versionedItem('Engine', ['/engine/v3/a', '/shared'], ['/engine/v2/a'])],
700
+ },
701
+ { tab: 'Solver', menu: [versionedItem('Solver', ['/solver/v3/a'], ['/solver/v2/a'])] },
702
+ ],
703
+ };
462
704
  function filterWith(decoratedNav, currentPath, currentVersion, currentLanguage, isPreview = false, userGroups = new Set()) {
463
705
  return filterDivisions({
464
706
  currentPath,
@@ -502,6 +744,60 @@ function assertChromeInvariantForEveryPath(nav, isPreview = false, userGroups =
502
744
  expect(mapHrefs(rescoped.firstHrefInLanguage), currentPath).toEqual(mapHrefs(full.firstHrefInLanguage));
503
745
  }
504
746
  }
747
+ const toLocale = (value) => locales.find((locale) => locale === value);
748
+ function sessionValuesContaining(node, href, key, current) {
749
+ const found = new Set();
750
+ if (Array.isArray(node)) {
751
+ for (const entry of node)
752
+ sessionValuesContaining(entry, href, key, current).forEach((v) => found.add(v));
753
+ return found;
754
+ }
755
+ if (typeof node !== 'object' || node === null)
756
+ return found;
757
+ const value = key in node ? Reflect.get(node, key) : undefined;
758
+ const scope = typeof value === 'string' ? value : current;
759
+ if (Reflect.get(node, 'href') === href)
760
+ found.add(scope);
761
+ for (const child of Object.values(node)) {
762
+ if (typeof child === 'object' && child !== null) {
763
+ sessionValuesContaining(child, href, key, scope).forEach((v) => found.add(v));
764
+ }
765
+ }
766
+ return found;
767
+ }
768
+ function activeChrome(result) {
769
+ return {
770
+ tabs: result.tabs.map(({ name, isActive, menu }) => ({
771
+ name,
772
+ isActive,
773
+ menu: menu === null || menu === void 0 ? void 0 : menu.map(({ name: item, isActive: active }) => ({ item, active })),
774
+ })),
775
+ versions: result.versions.map(({ name, isActive }) => ({ name, isActive })),
776
+ languages: result.languages.map(({ name, isActive }) => ({ name, isActive })),
777
+ dropdowns: result.dropdowns.map(({ name, isActive }) => ({ name, isActive })),
778
+ };
779
+ }
780
+ function assertChromeInvariantForEverySession(nav, key, isPreview = false, userGroups = new Set()) {
781
+ for (const path of getAllPathsInDecoratedNav(nav)) {
782
+ const currentPath = `/${path.replace(/^\//, '')}`;
783
+ const scoped = getScopedNavForPath({ decoratedNav: nav, currentPath, userGroups, isPreview });
784
+ const isCrossListed = JSON.stringify(nav).split(`"href":"${currentPath}"`).length > 2;
785
+ for (const value of sessionValuesContaining(nav, currentPath, key)) {
786
+ const version = key === 'version' ? value : scoped.currentVersion;
787
+ const language = key === 'language' ? toLocale(value) : scoped.currentLanguage;
788
+ const label = `${currentPath} ${key}=${value !== null && value !== void 0 ? value : '-'}`;
789
+ const full = filterWith(nav, currentPath, version, language, isPreview, userGroups);
790
+ const rescoped = filterWith(scoped.scopedNav, currentPath, version, language, isPreview, userGroups);
791
+ expect(rescoped.groupsOrPages, label).toEqual(full.groupsOrPages);
792
+ if (isCrossListed) {
793
+ expect(activeChrome(rescoped), label).toEqual(activeChrome(full));
794
+ }
795
+ else {
796
+ expect(chromeSummary(rescoped), label).toEqual(chromeSummary(full));
797
+ }
798
+ }
799
+ }
800
+ }
505
801
  function tabByName(nav, name) {
506
802
  if (!('tabs' in nav))
507
803
  throw new Error('nav has no tabs');
@@ -834,7 +1130,7 @@ describe('active-tab menu scoping', () => {
834
1130
  pages: [{ title: 'Get started', href: '/db/intro' }],
835
1131
  });
836
1132
  });
837
- it('keeps menu items containing versions whole and version-stubs inside them', () => {
1133
+ it('reduces menu items containing versions to a skeleton with one page per version', () => {
838
1134
  const { scopedNav } = getScopedNavForPath({
839
1135
  decoratedNav: menuWithVersionsTabNav,
840
1136
  currentPath: '/api/webhooks/events',
@@ -855,7 +1151,7 @@ describe('active-tab menu scoping', () => {
855
1151
  {
856
1152
  version: 'v2',
857
1153
  default: true,
858
- pages: [{ title: 'Users v2', href: '/api/v2/users' }],
1154
+ pages: [{ title: 'v2', href: '/api/v2/users' }],
859
1155
  },
860
1156
  {
861
1157
  version: 'v1',
@@ -864,6 +1160,134 @@ describe('active-tab menu scoping', () => {
864
1160
  ],
865
1161
  });
866
1162
  });
1163
+ it('filters identically for every path in a product menu with versions and dropdowns', () => {
1164
+ assertChromeInvariantForEveryPath(productMenuNav);
1165
+ assertChromeInvariantForEveryPath(productMenuNav, false, new Set(['staff']));
1166
+ assertChromeInvariantForEveryPath(productMenuNav, true);
1167
+ });
1168
+ it('resolves off-path menu hrefs identically for every session version and dropdown', () => {
1169
+ const documentation = tabByName(productMenuNav, 'Documentation');
1170
+ if (!('menu' in documentation))
1171
+ throw new Error('Documentation tab has no menu');
1172
+ const itemPaths = new Map(documentation.menu.map((item) => [
1173
+ item.item,
1174
+ getAllPathsInDecoratedNav(item),
1175
+ ]));
1176
+ const versions = [undefined, 'v3', 'v2', 'Legacy'];
1177
+ const dropdownCaches = [
1178
+ new Map(),
1179
+ new Map([['Documentation:Audio:OverviewAPI', 'audio/api/_autosummary/Clip.b']]),
1180
+ ];
1181
+ const gatedPaths = ['internal/runbook', 'studio/v3/api/internal'];
1182
+ const paths = getAllPathsInDecoratedNav(productMenuNav).filter((path) => !gatedPaths.includes(path.replace(/^\//, '')));
1183
+ for (const path of paths) {
1184
+ const currentPath = `/${path.replace(/^\//, '')}`;
1185
+ const { scopedNav } = getScopedNavForPath({
1186
+ decoratedNav: productMenuNav,
1187
+ currentPath,
1188
+ });
1189
+ const isOffPath = (name) => { var _a; return !((_a = itemPaths.get(name)) === null || _a === void 0 ? void 0 : _a.includes(path)); };
1190
+ for (const version of versions) {
1191
+ for (const dropdownCache of dropdownCaches) {
1192
+ const menuHrefs = (nav) => filterDivisions({
1193
+ currentPath,
1194
+ decoratedNav: nav,
1195
+ userGroups: new Set(),
1196
+ currentVersion: version,
1197
+ currentLanguage: undefined,
1198
+ shouldUseDivisionMatch: true,
1199
+ cache: { dropdownCache: new Map(dropdownCache) },
1200
+ }).tabs.map((tab) => { var _a; return (_a = tab.menu) === null || _a === void 0 ? void 0 : _a.filter((item) => isOffPath(item.name)).map((item) => [item.name, item.href]); });
1201
+ expect(menuHrefs(scopedNav), `${currentPath} ${version}`).toEqual(menuHrefs(productMenuNav));
1202
+ }
1203
+ }
1204
+ }
1205
+ });
1206
+ describe('pages cross-listed in several siblings', () => {
1207
+ it('filters identically for every session version across versioned menu items', () => {
1208
+ assertChromeInvariantForEverySession(crossListedMenuNav, 'version');
1209
+ });
1210
+ it('filters identically for every session language across menu items', () => {
1211
+ assertChromeInvariantForEverySession(crossListedLanguageMenuNav, 'language');
1212
+ });
1213
+ it('filters identically across plain menu items and across tabs', () => {
1214
+ assertChromeInvariantForEverySession(crossListedPlainMenuNav, 'version');
1215
+ assertChromeInvariantForEverySession(crossListedTabsNav, 'version');
1216
+ });
1217
+ it('filters identically when a containing menu item is hidden, for every viewer', () => {
1218
+ assertChromeInvariantForEverySession(crossListedHiddenMenuNav, 'version');
1219
+ assertChromeInvariantForEverySession(crossListedHiddenMenuNav, 'version', true);
1220
+ });
1221
+ it('keeps every menu item containing the page and skeletons the rest', () => {
1222
+ const { scopedNav } = getScopedNavForPath({
1223
+ decoratedNav: crossListedMenuNav,
1224
+ currentPath: '/shared',
1225
+ });
1226
+ const menu = tabByName(scopedNav, 'Docs');
1227
+ if (!('menu' in menu))
1228
+ throw new Error('Docs tab has no menu');
1229
+ const hrefs = (name) => getAllPathsInDecoratedNav(menu.menu.find((item) => item.item === name));
1230
+ expect(hrefs('Studio')).toContain('shared');
1231
+ expect(hrefs('Engine')).toEqual(expect.arrayContaining(['engine/v3/a', 'shared', 'engine/v3/c']));
1232
+ expect(hrefs('Solver')).toEqual(['solver/v3/a', 'solver/v2/a']);
1233
+ });
1234
+ });
1235
+ it('keeps openapi, asyncapi, and global on skeleton-stubbed menu items', () => {
1236
+ const global = { anchors: [{ anchor: 'Status', href: 'https://status.example.com' }] };
1237
+ const docsConfig = {
1238
+ $schema: 'https://mintlify.com/docs.json',
1239
+ name: 'Docs',
1240
+ theme: 'mint',
1241
+ colors: { primary: '#000000' },
1242
+ navigation: {
1243
+ tabs: [
1244
+ {
1245
+ tab: 'Docs',
1246
+ menu: [
1247
+ { item: 'Home', pages: ['home'] },
1248
+ {
1249
+ item: 'API',
1250
+ openapi: 'api/openapi.json',
1251
+ asyncapi: 'api/asyncapi.yaml',
1252
+ global,
1253
+ versions: [
1254
+ { version: 'v2', pages: ['api/v2/users'] },
1255
+ { version: 'v1', pages: ['api/v1/users'] },
1256
+ ],
1257
+ },
1258
+ ],
1259
+ },
1260
+ ],
1261
+ },
1262
+ };
1263
+ const { navigation } = scopeDocsConfigNavToPath(docsConfig, '/home');
1264
+ if (!('tabs' in navigation))
1265
+ throw new Error('nav has no tabs');
1266
+ const docs = navigation.tabs.find((tab) => tab.tab === 'Docs');
1267
+ if (!docs || !('menu' in docs))
1268
+ throw new Error('Docs tab has no menu');
1269
+ expect(docs.menu.find((item) => item.item === 'API')).toEqual({
1270
+ item: 'API',
1271
+ openapi: 'api/openapi.json',
1272
+ asyncapi: 'api/asyncapi.yaml',
1273
+ global,
1274
+ versions: [
1275
+ { version: 'v2', pages: ['api/v2/users'] },
1276
+ { version: 'v1', pages: ['api/v1/users'] },
1277
+ ],
1278
+ });
1279
+ });
1280
+ it('keeps no off-path api pages in a product menu', () => {
1281
+ const { scopedNav } = getScopedNavForPath({
1282
+ decoratedNav: productMenuNav,
1283
+ currentPath: '/home',
1284
+ });
1285
+ const paths = getAllPathsInDecoratedNav(scopedNav);
1286
+ expect(paths.filter((path) => path.includes('_autosummary'))).toEqual([
1287
+ 'engine/v3/py/_autosummary/Engine.a',
1288
+ 'studio/v3/api/_autosummary/Model.a',
1289
+ ]);
1290
+ });
867
1291
  it('carries hidden and groups onto off-path menu item stubs', () => {
868
1292
  const { scopedNav } = getScopedNavForPath({
869
1293
  decoratedNav: hiddenMenuTabNav,
@@ -0,0 +1 @@
1
+ export {};