diff --git a/packages/docusaurus-plugin-content-docs/src/client/__tests__/docsUtils.test.tsx b/packages/docusaurus-plugin-content-docs/src/client/__tests__/docsUtils.test.tsx index 00263ad5907d..5d8b30a0cc74 100644 --- a/packages/docusaurus-plugin-content-docs/src/client/__tests__/docsUtils.test.tsx +++ b/packages/docusaurus-plugin-content-docs/src/client/__tests__/docsUtils.test.tsx @@ -568,13 +568,28 @@ describe('useSidebarBreadcrumbs', () => { it('returns first level link', () => { const pathname = '/somePathName'; - const sidebar = [testCategory(), testLink({href: pathname})]; + const sidebar = [testCategory(), testLink({href: pathname, docId: 'doc1'})]; expect(createUseSidebarBreadcrumbsMock(sidebar)(pathname)).toEqual([ sidebar[1], ]); }); + it('returns doc links only', () => { + const pathname = '/somePathName'; + + // A link that is not a doc link should not appear in the breadcrumbs + // See https://github.com/facebook/docusaurus/pull/11616 + const nonDocLink = testLink({href: pathname}); + const docLink = testLink({href: pathname, docId: 'doc1'}); + + const sidebar = [testCategory(), nonDocLink, docLink]; + + expect(createUseSidebarBreadcrumbsMock(sidebar)(pathname)).toEqual([ + docLink, + ]); + }); + it('returns nested category', () => { const pathname = '/somePathName'; @@ -613,7 +628,7 @@ describe('useSidebarBreadcrumbs', () => { it('returns nested link', () => { const pathname = '/somePathName'; - const link = testLink({href: pathname}); + const link = testLink({href: pathname, docId: 'docNested'}); const categoryLevel3 = testCategory({ items: [testLink(), link, testLink()], @@ -657,6 +672,35 @@ describe('useSidebarBreadcrumbs', () => { createUseSidebarBreadcrumbsMock(undefined, false)('/foo'), ).toBeNull(); }); + + // Regression test for https://github.com/facebook/docusaurus/issues/11612 + it('returns the category that owns the URL, not a category with a link pointing to it', () => { + const categoryA: PropSidebarItemCategory = testCategory({ + label: 'Category A', + href: '/category-a', + items: [ + testLink({href: '/category-a/doc1', label: 'Doc 1'}), + testLink({href: '/category-a/doc2', label: 'Doc 2'}), + // This link points to Category B's generated-index + testLink({href: '/category-b', label: 'Go to Category B'}), + ], + }); + + const categoryB: PropSidebarItemCategory = testCategory({ + label: 'Category B', + href: '/category-b', + items: [ + testLink({href: '/category-b/item1', label: 'Item 1'}), + testLink({href: '/category-b/item2', label: 'Item 2'}), + ], + }); + + const sidebar: PropSidebar = [categoryA, categoryB]; + + expect(createUseSidebarBreadcrumbsMock(sidebar)('/category-b')).toEqual([ + categoryB, + ]); + }); }); describe('useCurrentSidebarCategory', () => { @@ -708,12 +752,16 @@ describe('useCurrentSidebarCategory', () => { expect(mockUseCurrentSidebarCategory('/cat2')).toEqual(category2); }); - it('works for category link item', () => { - const link = testLink({href: '/my/link/path'}); + it('works for category doc link item', () => { + const pathname = '/my/link/path'; + const nonDocLink = testLink({href: pathname}); + const docLink = testLink({href: pathname, docId: 'doc1'}); + const category: PropSidebarItemCategory = testCategory({ href: '/cat1', - items: [testLink(), testLink(), link, testCategory()], + items: [testLink(), testLink(), nonDocLink, docLink, testCategory()], }); + const sidebar: PropSidebar = [ testLink(), testLink(), @@ -724,18 +772,28 @@ describe('useCurrentSidebarCategory', () => { const mockUseCurrentSidebarCategory = createUseCurrentSidebarCategoryMock(sidebar); - expect(mockUseCurrentSidebarCategory('/my/link/path')).toEqual(category); + expect(mockUseCurrentSidebarCategory(pathname)).toEqual(category); }); it('works for nested category link item', () => { - const link = testLink({href: '/my/link/path'}); + const pathname = '/my/link/path'; + const nonDocLink = testLink({href: pathname}); + const docLink = testLink({href: pathname, docId: 'doc1'}); + const category2: PropSidebarItemCategory = testCategory({ href: '/cat2', - items: [testLink(), testLink(), link, testCategory()], + items: [ + testLink(), + testLink(), + testCategory({items: [nonDocLink]}), + nonDocLink, + docLink, + testCategory(), + ], }); const category1: PropSidebarItemCategory = testCategory({ href: '/cat1', - items: [testLink(), testLink(), category2, testCategory()], + items: [testLink(), nonDocLink, testLink(), category2, testCategory()], }); const sidebar: PropSidebar = [ testLink(), @@ -780,6 +838,38 @@ describe('useCurrentSidebarCategory', () => { `"Unexpected: cant find current sidebar in context"`, ); }); + + // Regression test for https://github.com/facebook/docusaurus/issues/11612 + it('returns the category that owns the URL, not a category with a link pointing to it', () => { + const categoryA: PropSidebarItemCategory = testCategory({ + label: 'Category A', + href: '/category-a', + items: [ + testLink({href: '/category-a/doc1', label: 'Doc 1'}), + testLink({href: '/category-a/doc2', label: 'Doc 2'}), + // This link points to Category B's generated-index + testLink({href: '/category-b', label: 'Go to Category B'}), + ], + }); + + const categoryB: PropSidebarItemCategory = testCategory({ + label: 'Category B', + href: '/category-b', + items: [ + testLink({href: '/category-b/item1', label: 'Item 1'}), + testLink({href: '/category-b/item2', label: 'Item 2'}), + ], + }); + + const sidebar: PropSidebar = [categoryA, categoryB]; + + const mockUseCurrentSidebarCategory = + createUseCurrentSidebarCategoryMock(sidebar); + + // When visiting /category-b, we should get Category B (the owner), + // not Category A (which just has a link to it) + expect(mockUseCurrentSidebarCategory('/category-b')).toEqual(categoryB); + }); }); describe('useCurrentSidebarSiblings', () => { @@ -805,10 +895,10 @@ describe('useCurrentSidebarSiblings', () => { testCategory(), ]; - const mockUseCurrentSidebarCategory = + const mockUseCurrentSidebarSiblings = createUseCurrentSidebarSiblingsMock(sidebar); - expect(mockUseCurrentSidebarCategory('/cat')).toEqual(category.items); + expect(mockUseCurrentSidebarSiblings('/cat')).toEqual(category.items); }); it('works for sidebar root', () => { @@ -823,10 +913,10 @@ describe('useCurrentSidebarSiblings', () => { testCategory(), ]; - const mockUseCurrentSidebarCategory = + const mockUseCurrentSidebarSiblings = createUseCurrentSidebarSiblingsMock(sidebar); - expect(mockUseCurrentSidebarCategory('/rootLink')).toEqual(sidebar); + expect(mockUseCurrentSidebarSiblings('/rootLink')).toEqual(sidebar); }); it('works for nested sidebar category', () => { @@ -852,10 +942,13 @@ describe('useCurrentSidebarSiblings', () => { }); it('works for category link item', () => { - const link = testLink({href: '/my/link/path'}); + const pathname = '/my/link/path'; + const nonDocLink = testLink({href: pathname}); + const docLink = testLink({href: pathname, docId: 'doc1'}); + const category: PropSidebarItemCategory = testCategory({ href: '/cat1', - items: [testLink(), testLink(), link, testCategory()], + items: [testLink(), testLink(), nonDocLink, docLink, testCategory()], }); const sidebar: PropSidebar = [ testLink(), @@ -864,23 +957,24 @@ describe('useCurrentSidebarSiblings', () => { testCategory(), ]; - const mockUseCurrentSidebarCategory = + const mockUseCurrentSidebarSiblings = createUseCurrentSidebarSiblingsMock(sidebar); - expect(mockUseCurrentSidebarCategory('/my/link/path')).toEqual( - category.items, - ); + expect(mockUseCurrentSidebarSiblings(pathname)).toEqual(category.items); }); it('works for nested category link item', () => { - const link = testLink({href: '/my/link/path'}); + const pathname = '/my/link/path'; + const nonDocLink = testLink({href: pathname}); + const docLink = testLink({href: pathname, docId: 'doc1'}); + const category2: PropSidebarItemCategory = testCategory({ href: '/cat2', - items: [testLink(), testLink(), link, testCategory()], + items: [testLink(), testLink(), nonDocLink, testCategory()], }); const category1: PropSidebarItemCategory = testCategory({ href: '/cat1', - items: [testLink(), testLink(), category2, testCategory()], + items: [testLink(), testLink(), category2, docLink, testCategory()], }); const sidebar: PropSidebar = [ testLink(), @@ -889,18 +983,16 @@ describe('useCurrentSidebarSiblings', () => { testCategory(), ]; - const mockUseCurrentSidebarCategory = + const mockUseCurrentSidebarSiblings = createUseCurrentSidebarSiblingsMock(sidebar); - expect(mockUseCurrentSidebarCategory('/my/link/path')).toEqual( - category2.items, - ); + expect(mockUseCurrentSidebarSiblings(pathname)).toEqual(category1.items); }); it('throws when sidebar is missing', () => { - const mockUseCurrentSidebarCategory = createUseCurrentSidebarSiblingsMock(); + const mockUseCurrentSidebarSiblings = createUseCurrentSidebarSiblingsMock(); expect(() => - mockUseCurrentSidebarCategory('/cat'), + mockUseCurrentSidebarSiblings('/cat'), ).toThrowErrorMatchingInlineSnapshot( `"Unexpected: cant find current sidebar in context"`, ); diff --git a/packages/docusaurus-plugin-content-docs/src/client/docsUtils.tsx b/packages/docusaurus-plugin-content-docs/src/client/docsUtils.tsx index aa00df851034..474a58895ade 100644 --- a/packages/docusaurus-plugin-content-docs/src/client/docsUtils.tsx +++ b/packages/docusaurus-plugin-content-docs/src/client/docsUtils.tsx @@ -234,15 +234,22 @@ function getSidebarBreadcrumbs({ }): PropSidebarBreadcrumbsItem[] { const breadcrumbs: PropSidebarBreadcrumbsItem[] = []; - function extract(items: PropSidebarItem[]) { + function extract(items: PropSidebarItem[]): boolean { for (const item of items) { - if ( - (item.type === 'category' && - (isSamePath(item.href, pathname) || extract(item.items))) || - (item.type === 'link' && isSamePath(item.href, pathname)) + // Extract category item + if (item.type === 'category') { + if (isSamePath(item.href, pathname) || extract(item.items)) { + breadcrumbs.unshift(item); + return true; + } + } + // Extract doc item + else if ( + item.type === 'link' && + item.docId && + isSamePath(item.href, pathname) ) { - const filtered = onlyCategories && item.type !== 'category'; - if (!filtered) { + if (!onlyCategories) { breadcrumbs.unshift(item); } return true;