@@ -27,8 +27,8 @@ public async Task UpdateRelativeUrlUsesNavigationPathWhenAssemblerBuildEnabled()
2727 var nonAssemblerResult = await ResolveUrlForBuildMode ( relativeAssetPath , assemblerBuild : false , pathPrefix : "this-is-not-relevant" ) ;
2828 var assemblerResult = await ResolveUrlForBuildMode ( relativeAssetPath , assemblerBuild : true , pathPrefix : "platform" ) ;
2929
30- nonAssemblerResult . Should ( ) . Be ( "/docs/setup/images/pic.png" ) ;
31- assemblerResult . Should ( ) . Be ( "/docs/platform/setup/images/pic.png" ) ;
30+ nonAssemblerResult . Should ( ) . AllBe ( "/docs/setup/images/pic.png" ) ;
31+ assemblerResult . Should ( ) . AllBe ( "/docs/platform/setup/images/pic.png" ) ;
3232 }
3333
3434 [ Fact ]
@@ -37,7 +37,7 @@ public async Task UpdateRelativeUrlWithoutPathPrefixKeepsGlobalPrefix()
3737 var relativeAssetPath = "images/funny-image.png" ;
3838 var assemblerResult = await ResolveUrlForBuildMode ( relativeAssetPath , assemblerBuild : true , pathPrefix : null ) ;
3939
40- assemblerResult . Should ( ) . Be ( "/docs/setup/images/funny-image.png" ) ;
40+ assemblerResult . Should ( ) . AllBe ( "/docs/setup/images/funny-image.png" ) ;
4141 }
4242
4343 [ Fact ]
@@ -46,16 +46,15 @@ public async Task UpdateRelativeUrlAppliesCustomPathPrefix()
4646 var relativeAssetPath = "images/image.png" ;
4747 var assemblerResult = await ResolveUrlForBuildMode ( relativeAssetPath , assemblerBuild : true , pathPrefix : "custom" ) ;
4848
49- assemblerResult . Should ( ) . Be ( "/docs/custom/setup/images/image.png" ) ;
49+ assemblerResult . Should ( ) . AllBe ( "/docs/custom/setup/images/image.png" ) ;
5050 }
5151
5252 /// <summary>
5353 /// Resolves a relative asset URL the same way the assembler would for a single markdown file, using the provided navigation path prefix.
5454 /// </summary>
55- private async Task < string > ResolveUrlForBuildMode ( string relativeAssetPath , bool assemblerBuild , string ? pathPrefix )
55+ private async Task < string [ ] > ResolveUrlForBuildMode ( string relativeAssetPath , bool assemblerBuild , string ? pathPrefix )
5656 {
5757 const string guideRelativePath = "setup/guide.md" ;
58- var navigationUrl = BuildNavigationUrl ( pathPrefix , guideRelativePath ) ;
5958 var files = new Dictionary < string , MockFileData >
6059 {
6160 [ "docs/docset.yml" ] = new (
@@ -66,7 +65,13 @@ private async Task<string> ResolveUrlForBuildMode(string relativeAssetPath, bool
6665 - file: { guideRelativePath }
6766 """
6867 ) ,
69- [ "docs/index.md" ] = new ( "# Home" ) ,
68+ [ "docs/index.md" ] = new (
69+ $ """
70+ # Home
71+
72+ 
73+ """
74+ ) ,
7075 [ "docs/" + guideRelativePath ] = new (
7176 $ """
7277 # Guide
@@ -97,38 +102,45 @@ private async Task<string> ResolveUrlForBuildMode(string relativeAssetPath, bool
97102 await documentationSet . ResolveDirectoryTree ( TestContext . Current . CancellationToken ) ;
98103
99104 // Normalize path for cross-platform compatibility (Windows uses backslashes)
100- var normalizedPath = guideRelativePath . Replace ( '/' , Path . DirectorySeparatorChar ) ;
101- if ( documentationSet . TryFindDocumentByRelativePath ( normalizedPath ) is not MarkdownFile markdownFile )
102- throw new InvalidOperationException ( $ "Failed to resolve markdown file for test. Tried path: { normalizedPath } ") ;
103-
104- // For assembler builds DocumentationSetNavigation seeds MarkdownNavigationLookup with navigation items whose Url already
105- // includes the computed path_prefix. To exercise the same branch in isolation, inject a stub navigation entry with the
106- // expected Url (and minimal metadata for the surrounding API contract).
107- _ = documentationSet . NavigationDocumentationFileLookup . Remove ( markdownFile ) ;
108- documentationSet . NavigationDocumentationFileLookup . Add ( markdownFile , new NavigationItemStub ( navigationUrl ) ) ;
109- documentationSet . NavigationDocumentationFileLookup . TryGetValue ( markdownFile , out var navigation ) . Should ( )
110- . BeTrue ( "navigation lookup should contain current page" ) ;
111- navigation ? . Url . Should ( ) . Be ( navigationUrl ) ;
112-
113- var parserState = new ParserState ( buildContext )
105+ ( string , string ) [ ] pathsToTest = [ ( guideRelativePath . Replace ( '/' , Path . DirectorySeparatorChar ) , relativeAssetPath ) , ( "index.md" , $ "setup{ Path . DirectorySeparatorChar } { relativeAssetPath } ") ] ;
106+ List < string > toReturn = [ ] ;
107+
108+ foreach ( var normalizedPath in pathsToTest )
114109 {
115- MarkdownSourcePath = markdownFile . SourceFile ,
116- YamlFrontMatter = null ,
117- CrossLinkResolver = documentationSet . CrossLinkResolver ,
118- TryFindDocument = file => documentationSet . TryFindDocument ( file ) ,
119- TryFindDocumentByRelativePath = path => documentationSet . TryFindDocumentByRelativePath ( path ) ,
120- NavigationTraversable = documentationSet
121- } ;
110+ if ( documentationSet . TryFindDocumentByRelativePath ( normalizedPath . Item1 ) is not MarkdownFile markdownFile )
111+ throw new InvalidOperationException ( $ "Failed to resolve markdown file for test. Tried path: { normalizedPath } ") ;
112+
113+ var navigationUrl = BuildNavigationUrl ( pathPrefix , normalizedPath . Item1 ) ;
114+ // For assembler builds DocumentationSetNavigation seeds MarkdownNavigationLookup with navigation items whose Url already
115+ // includes the computed path_prefix. To exercise the same branch in isolation, inject a stub navigation entry with the
116+ // expected Url (and minimal metadata for the surrounding API contract).
117+ _ = documentationSet . NavigationDocumentationFileLookup . Remove ( markdownFile ) ;
118+ documentationSet . NavigationDocumentationFileLookup . Add ( markdownFile , new NavigationItemStub ( navigationUrl ) ) ;
119+ documentationSet . NavigationDocumentationFileLookup . TryGetValue ( markdownFile , out var navigation ) . Should ( )
120+ . BeTrue ( "navigation lookup should contain current page" ) ;
121+ navigation ? . Url . Should ( ) . Be ( navigationUrl ) ;
122+
123+ var parserState = new ParserState ( buildContext )
124+ {
125+ MarkdownSourcePath = markdownFile . SourceFile ,
126+ YamlFrontMatter = null ,
127+ CrossLinkResolver = documentationSet . CrossLinkResolver ,
128+ TryFindDocument = file => documentationSet . TryFindDocument ( file ) ,
129+ TryFindDocumentByRelativePath = path => documentationSet . TryFindDocumentByRelativePath ( path ) ,
130+ NavigationTraversable = documentationSet
131+ } ;
132+
133+ var context = new ParserContext ( parserState ) ;
134+ context . TryFindDocument ( context . MarkdownSourcePath ) . Should ( ) . BeSameAs ( markdownFile ) ;
135+ context . Build . AssemblerBuild . Should ( ) . Be ( assemblerBuild ) ;
122136
123- var context = new ParserContext ( parserState ) ;
124- context . TryFindDocument ( context . MarkdownSourcePath ) . Should ( ) . BeSameAs ( markdownFile ) ;
125- context . Build . AssemblerBuild . Should ( ) . Be ( assemblerBuild ) ;
137+ toReturn . Add ( DiagnosticLinkInlineParser . UpdateRelativeUrl ( context , normalizedPath . Item2 ) ) ;
126138
127- var resolved = DiagnosticLinkInlineParser . UpdateRelativeUrl ( context , relativeAssetPath ) ;
139+ }
128140
129141 await collector . StopAsync ( TestContext . Current . CancellationToken ) ;
130142
131- return resolved ;
143+ return toReturn . ToArray ( ) ;
132144 }
133145
134146 /// <summary>
@@ -142,6 +154,12 @@ private static string BuildNavigationUrl(string? pathPrefix, string docRelativeP
142154 if ( docPath . EndsWith ( ".md" , StringComparison . OrdinalIgnoreCase ) )
143155 docPath = docPath [ ..^ 3 ] ;
144156
157+ // Handle index.md
158+ if ( docPath . EndsWith ( "/index" , StringComparison . OrdinalIgnoreCase ) )
159+ docPath = docPath [ ..^ 6 ] ;
160+ else if ( docPath . Equals ( "index" , StringComparison . OrdinalIgnoreCase ) )
161+ docPath = string . Empty ;
162+
145163 var segments = new List < string > ( ) ;
146164 if ( ! string . IsNullOrWhiteSpace ( pathPrefix ) )
147165 segments . Add ( pathPrefix . Trim ( '/' ) ) ;
0 commit comments