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 00263ad590..11b836b758 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 @@ -780,6 +780,43 @@ describe('useCurrentSidebarCategory', () => { `"Unexpected: cant find current sidebar in context"`, ); }); + + // 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', + 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 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', () => { diff --git a/packages/docusaurus-plugin-content-docs/src/client/docsUtils.tsx b/packages/docusaurus-plugin-content-docs/src/client/docsUtils.tsx index aa00df8510..1ff2f339ee 100644 --- a/packages/docusaurus-plugin-content-docs/src/client/docsUtils.tsx +++ b/packages/docusaurus-plugin-content-docs/src/client/docsUtils.tsx @@ -234,11 +234,40 @@ function getSidebarBreadcrumbs({ }): PropSidebarBreadcrumbsItem[] { const breadcrumbs: PropSidebarBreadcrumbsItem[] = []; - function extract(items: PropSidebarItem[]) { + // 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 { + for (const item of items) { + if (item.type === 'category') { + if (isSamePath(item.href, pathname)) { + breadcrumbs.unshift(item); + return true; + } + if (extractCategoryOnly(item.items)) { + breadcrumbs.unshift(item); + return true; + } + } + } + return false; + } + + function extractWithLinks(items: PropSidebarItem[]): boolean { for (const item of items) { if ( (item.type === 'category' && - (isSamePath(item.href, pathname) || extract(item.items))) || + (isSamePath(item.href, pathname) || extractWithLinks(item.items))) || (item.type === 'link' && isSamePath(item.href, pathname)) ) { const filtered = onlyCategories && item.type !== 'category'; @@ -248,11 +277,19 @@ function getSidebarBreadcrumbs({ return true; } } - return false; } - extract(sidebarItems); + 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); + } return breadcrumbs; }