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 11b836b758..ce381c5d0a 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 @@ -657,6 +657,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', () => { @@ -782,21 +811,7 @@ describe('useCurrentSidebarCategory', () => { }); // Regression test for https://github.com/facebook/docusaurus/issues/11612 - // When a link in Category A points to a generated-index URL owned by - // Category B, useCurrentSidebarCategory should return Category B (the owner), - // not Category A (which merely has a link pointing to it). it('returns the category that owns the URL, not a category with a link pointing to it', () => { - // Category B is the actual owner of /category-b (its generated-index href) - 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'}), - ], - }); - - // Category A contains a link that points to Category B's URL const categoryA: PropSidebarItemCategory = testCategory({ label: 'Category A', href: '/category-a', @@ -808,6 +823,15 @@ describe('useCurrentSidebarCategory', () => { ], }); + 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 = diff --git a/packages/docusaurus-plugin-content-docs/src/client/docsUtils.tsx b/packages/docusaurus-plugin-content-docs/src/client/docsUtils.tsx index 1ff2f339ee..725eaee60f 100644 --- a/packages/docusaurus-plugin-content-docs/src/client/docsUtils.tsx +++ b/packages/docusaurus-plugin-content-docs/src/client/docsUtils.tsx @@ -234,27 +234,14 @@ function getSidebarBreadcrumbs({ }): PropSidebarBreadcrumbsItem[] { const breadcrumbs: PropSidebarBreadcrumbsItem[] = []; - // When onlyCategories is true (e.g., for useCurrentSidebarCategory), we need - // to distinguish between: - // 1. A category that directly owns this URL (category.href === pathname) - // 2. A link that happens to point to this URL - // - // We should prefer (1) over (2) to fix the bug where a generated-index page - // shows items from the wrong category when another category has a link - // pointing to it. See https://github.com/facebook/docusaurus/issues/11612 - // - // We use a two-pass approach: - // - First pass: Only look for categories that directly own the URL - // - Second pass: If not found, look for links (to support doc pages) - - function extractCategoryOnly(items: PropSidebarItem[]): boolean { + function extractCategory(items: PropSidebarItem[]): boolean { for (const item of items) { if (item.type === 'category') { if (isSamePath(item.href, pathname)) { breadcrumbs.unshift(item); return true; } - if (extractCategoryOnly(item.items)) { + if (extractCategory(item.items)) { breadcrumbs.unshift(item); return true; } @@ -263,11 +250,11 @@ function getSidebarBreadcrumbs({ return false; } - function extractWithLinks(items: PropSidebarItem[]): boolean { + function extract(items: PropSidebarItem[]): boolean { for (const item of items) { if ( (item.type === 'category' && - (isSamePath(item.href, pathname) || extractWithLinks(item.items))) || + (isSamePath(item.href, pathname) || extract(item.items))) || (item.type === 'link' && isSamePath(item.href, pathname)) ) { const filtered = onlyCategories && item.type !== 'category'; @@ -280,16 +267,11 @@ function getSidebarBreadcrumbs({ return false; } - if (onlyCategories) { - // First try to find a category that directly owns this URL - if (!extractCategoryOnly(sidebarItems)) { - // Fall back to finding via links (for doc pages in a category) - extractWithLinks(sidebarItems); - } - } else { - // For breadcrumbs, use the original behavior (links included) - extractWithLinks(sidebarItems); - } + // We use a two-pass approach: + // - First pass: Only look for categories that directly own the URL + // - Second pass: If not found, look for links (to support doc pages) + // See why here: https://github.com/facebook/docusaurus/issues/11612 + extractCategory(sidebarItems) || extract(sidebarItems); return breadcrumbs; }