Skip to content

Commit 092ae43

Browse files
authored
Prefer visible folder children when deriving navigation indexes (#3451)
* Initial plan * Add hidden folder navigation safeguards * Finish hidden navigation index fix --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent a074566 commit 092ae43

4 files changed

Lines changed: 66 additions & 4 deletions

File tree

src/Elastic.Documentation.Navigation/Isolated/Node/FolderNavigation.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class FolderNavigation<TModel>(
3232
public INodeNavigationItem<INavigationModel, INavigationItem>? Parent { get; set; } = parent;
3333

3434
/// <inheritdoc />
35-
public bool Hidden { get; }
35+
public bool Hidden { get; private set; }
3636

3737
/// <inheritdoc />
3838
public int NavigationIndex { get; set; }
@@ -50,6 +50,7 @@ internal void SetNavigationItems(IReadOnlyCollection<INavigationItem> navigation
5050
{
5151
var indexNavigation = navigationItems.QueryIndex<TModel>(this, $"{FolderPath}/index.md", out navigationItems);
5252
Index = indexNavigation;
53+
Hidden = Index.Hidden;
5354
// Include NavigationRoot.Id to ensure uniqueness across docsets in assembler builds
5455
// (URLs alone aren't unique until path prefixes are applied by SiteNavigation)
5556
Id = ShortId.Create(NavigationRoot.Id, Index.Url);

src/Elastic.Documentation.Navigation/Isolated/Node/TableOfContentsNavigation.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ INavigationHomeProvider homeProvider
7777
public INavigationHomeProvider HomeProvider { get; set; }
7878

7979
/// <inheritdoc />
80-
public bool Hidden { get; }
80+
public bool Hidden { get; private set; }
8181

8282
/// <inheritdoc />
8383
public int NavigationIndex { get; set; }
@@ -104,6 +104,7 @@ internal void SetNavigationItems(IReadOnlyCollection<INavigationItem> navigation
104104
{
105105
var indexNavigation = navigationItems.QueryIndex<TModel>(this, $"{ParentPath}/index.md", out navigationItems);
106106
Index = indexNavigation;
107+
Hidden = Index.Hidden;
107108
// Include NavigationRoot.Id to ensure uniqueness across docsets in assembler builds
108109
Id = ShortId.Create(NavigationRoot.Id, Index.Url);
109110
NavigationItems = navigationItems;

src/Elastic.Documentation.Navigation/NavigationItemExtensions.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,21 @@ this IReadOnlyCollection<INavigationItem> items, INodeNavigationItem<INavigation
1616
)
1717
where TModel : class, IDocumentationFile
1818
{
19-
var index = LookupIndex();
19+
var index = LookupIndex(preferVisible: true);
20+
index ??= LookupIndex(preferVisible: false);
21+
ArgumentNullException.ThrowIfNull(index);
2022

2123
children = items.Except([index]).ToArray();
2224

2325
return index;
2426

25-
ILeafNavigationItem<TModel> LookupIndex()
27+
ILeafNavigationItem<TModel>? LookupIndex(bool preferVisible)
2628
{
2729
foreach (var item in items)
2830
{
31+
if (preferVisible && item.Hidden)
32+
continue;
33+
2934
// Check for the exact type match
3035
if (item is ILeafNavigationItem<TModel> leaf)
3136
return leaf;
@@ -35,6 +40,9 @@ ILeafNavigationItem<TModel> LookupIndex()
3540
return nodeItem.Index;
3641
}
3742

43+
if (preferVisible)
44+
return null;
45+
3846
// If no index is found, throw an exception
3947
throw new InvalidOperationException($"No index found for navigation node '{node.GetType().Name}' at path '{fallbackPath}'");
4048
}

tests/Navigation.Tests/Isolation/DynamicUrlTests.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,58 @@ public void FolderWithoutIndexUsesFirstChildUrl()
113113
folder!.Url.Should().Be("/guides/getting-started");
114114
}
115115

116+
[Fact]
117+
public void FolderWithoutIndexUsesFirstVisibleChildUrlWhenHiddenChildComesFirst()
118+
{
119+
// language=yaml
120+
var yaml = """
121+
project: 'test-project'
122+
toc:
123+
- folder: guides
124+
children:
125+
- hidden: autopilot.md
126+
- file: getting-started.md
127+
""";
128+
129+
var fileSystem = new MockFileSystem();
130+
fileSystem.AddDirectory("/docs");
131+
var context = CreateContext();
132+
var docSet = DocumentationSetFile.LoadAndResolve(context.Collector, yaml, fileSystem.NewDirInfo("docs"));
133+
134+
var navigation = new DocumentationSetNavigation<IDocumentationFile>(docSet, context, GenericDocumentationFileFactory.Instance);
135+
var folder = navigation.NavigationItems.First().Should().BeOfType<FolderNavigation<IDocumentationFile>>().Subject;
136+
137+
folder.Hidden.Should().BeFalse();
138+
folder.NavigationTitle.Should().Be("getting-started");
139+
folder.Url.Should().Be("/guides/getting-started");
140+
folder.NavigationItems.Should().ContainSingle().Which.Hidden.Should().BeTrue();
141+
}
142+
143+
[Fact]
144+
public void FolderWithoutIndexAndOnlyHiddenChildrenIsHidden()
145+
{
146+
// language=yaml
147+
var yaml = """
148+
project: 'test-project'
149+
toc:
150+
- folder: guides
151+
children:
152+
- hidden: autopilot.md
153+
""";
154+
155+
var fileSystem = new MockFileSystem();
156+
fileSystem.AddDirectory("/docs");
157+
var context = CreateContext();
158+
var docSet = DocumentationSetFile.LoadAndResolve(context.Collector, yaml, fileSystem.NewDirInfo("docs"));
159+
160+
var navigation = new DocumentationSetNavigation<IDocumentationFile>(docSet, context, GenericDocumentationFileFactory.Instance);
161+
var folder = navigation.NavigationItems.First().Should().BeOfType<FolderNavigation<IDocumentationFile>>().Subject;
162+
163+
folder.Hidden.Should().BeTrue();
164+
folder.Index.Hidden.Should().BeTrue();
165+
folder.NavigationItems.Should().BeEmpty();
166+
}
167+
116168
[Fact]
117169
public void FolderWithNestedChildren()
118170
{

0 commit comments

Comments
 (0)