Skip to content

Commit 9322d4a

Browse files
committed
Fix remaining test errors after the update, and introduce test cases for complex anchor scenarios.
1 parent f29bacf commit 9322d4a

File tree

5 files changed

+192
-6
lines changed

5 files changed

+192
-6
lines changed

src/Elastic.Markdown/Links/CrossLinks/CrossLinkResolver.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static bool TryResolve(
5252

5353
if (!fetchedCrossLinks.LinkReferences.TryGetValue(crossLinkUri.Scheme, out var sourceLinkReference))
5454
{
55-
errorEmitter($"'{crossLinkUri.Scheme}' was not found in the cross link index.");
55+
errorEmitter($"'{crossLinkUri.Scheme}' was not found in the cross link index");
5656
return false;
5757
}
5858

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
namespace authoring
6+
7+
open System
8+
open System.Collections.Generic
9+
open System.Collections.Frozen
10+
open System.IO.Abstractions.TestingHelpers
11+
open Elastic.Documentation.Diagnostics
12+
open Elastic.Documentation.Links
13+
open Elastic.Markdown.Links.CrossLinks
14+
open Elastic.Documentation
15+
open Swensen.Unquote
16+
open Elastic.Documentation.Configuration.Builder
17+
open authoring
18+
19+
module CrossLinkResolverAssertions =
20+
21+
let private parseRedirectsYaml (redirectsYamlContent: string) (collector: IDiagnosticsCollector) =
22+
let mockFileSystem = MockFileSystem()
23+
let fullYaml = sprintf "redirects:\n%s" (redirectsYamlContent.Replace("\r\n", "\n").Replace("\n", "\n "))
24+
let mockRedirectsFilePath = "mock_redirects.yml"
25+
mockFileSystem.AddFile(mockRedirectsFilePath, MockFileData(fullYaml))
26+
let mockRedirectsFile = mockFileSystem.FileInfo.New(mockRedirectsFilePath)
27+
28+
let docContext =
29+
{ new IDocumentationContext with
30+
member _.Collector = collector
31+
member _.DocumentationSourceDirectory = mockFileSystem.DirectoryInfo.New("/docs")
32+
member _.Git = GitCheckoutInformation.Unavailable
33+
member _.ReadFileSystem = mockFileSystem
34+
member _.WriteFileSystem = mockFileSystem
35+
member _.ConfigurationPath = mockFileSystem.FileInfo.New("mock_docset.yml")
36+
}
37+
let redirectFileParser = RedirectFile(mockRedirectsFile, docContext)
38+
redirectFileParser.Redirects
39+
40+
// Helper to create FetchedCrossLinks
41+
let private createFetchedCrossLinks (redirectsYamlSnippet: string) (linksData: IDictionary<string, LinkMetadata>) repoName =
42+
let collector = TestDiagnosticsCollector() :> IDiagnosticsCollector
43+
let redirectRules = parseRedirectsYaml redirectsYamlSnippet collector
44+
45+
if collector.Errors > 0 then
46+
failwithf $"Failed to parse redirects YAML: %A{collector}"
47+
48+
let linkReference =
49+
LinkReference(
50+
Origin = GitCheckoutInformation.Unavailable,
51+
UrlPathPrefix = null,
52+
Links = Dictionary(linksData),
53+
CrossLinks = Array.empty<string>,
54+
Redirects = redirectRules
55+
)
56+
57+
let declaredRepos = HashSet<string>()
58+
declaredRepos.Add(repoName) |> ignore
59+
60+
FetchedCrossLinks(
61+
DeclaredRepositories = declaredRepos,
62+
LinkReferences = FrozenDictionary.ToFrozenDictionary(dict [repoName, linkReference]),
63+
FromConfiguration = true,
64+
LinkIndexEntries = FrozenDictionary<string, LinkRegistryEntry>.Empty
65+
)
66+
67+
let private redirectsYaml = """
68+
# test scenario 1
69+
'testing/redirects/multi-topic-page-1-old.md':
70+
to: 'testing/redirects/multi-topic-page-1-new-anchorless.md'
71+
anchors: { "!": null }
72+
many:
73+
- to: 'testing/redirects/multi-topic-page-1-new-topic-a-subpage.md'
74+
anchors: {'topic-a-intro': null, 'topic-a-details': 'details-anchor'}
75+
- to: 'testing/redirects/multi-topic-page-1-new-topic-b-subpage.md'
76+
anchors: {'topic-b-main': 'main-anchor'}
77+
- to: 'testing/redirects/multi-topic-page-1-old.md'
78+
anchors: {'topic-c-main': 'topic-c-main'}
79+
# test scenario 2
80+
'testing/redirects/multi-topic-page-2-old.md':
81+
to: 'testing/redirects/multi-topic-page-2-old.md'
82+
anchors: {} # This means pass through any anchor for the default 'to'
83+
many:
84+
- to: 'testing/redirects/multi-topic-page-2-new-topic-a-subpage.md'
85+
anchors: {'topic-a-intro': 'introduction', 'topic-a-details': null}
86+
- to: 'testing/redirects/multi-topic-page-2-new-topic-b-subpage.md'
87+
anchors: {'topic-b-main': 'summary', 'topic-b-config': null}
88+
"""
89+
90+
let private mockLinksData =
91+
dict [
92+
("testing/redirects/multi-topic-page-1-new-anchorless.md", LinkMetadata(Anchors = null, Hidden = false))
93+
("testing/redirects/multi-topic-page-1-new-topic-a-subpage.md", LinkMetadata(Anchors = [| "details-anchor"; "introduction" |], Hidden = false))
94+
("testing/redirects/multi-topic-page-1-new-topic-b-subpage.md", LinkMetadata(Anchors = [| "main-anchor" |], Hidden = false))
95+
("testing/redirects/multi-topic-page-1-old.md", LinkMetadata(Anchors = [| "topic-c-main"; "unmatched-anchor" |], Hidden = false))
96+
("testing/redirects/multi-topic-page-2-old.md", LinkMetadata(Anchors = [| "unmatched-anchor"; "topic-c-main"; "topic-a-intro"; "topic-a-details"; "topic-b-main"; "topic-b-config" |], Hidden = false))
97+
("testing/redirects/multi-topic-page-2-new-topic-a-subpage.md", LinkMetadata(Anchors = [| "introduction"; "summary" |], Hidden = false))
98+
("testing/redirects/multi-topic-page-2-new-topic-b-subpage.md", LinkMetadata(Anchors = [| "summary" |], Hidden = false))
99+
] :> IDictionary<_,_>
100+
101+
102+
let private repoName = "docs-content"
103+
let private fetchedLinks = createFetchedCrossLinks redirectsYaml mockLinksData repoName
104+
let private uriResolver = IsolatedBuildEnvironmentUriResolver() :> IUriEnvironmentResolver
105+
let private baseExpectedUrl = $"https://docs-v3-preview.elastic.dev/elastic/{repoName}/tree/main"
106+
107+
let resolvesTo (inputUrl: string) (expectedPathWithOptionalAnchor: string) =
108+
let mutable errors = List.empty
109+
let errorEmitter (msg: string) = errors <- msg :: errors
110+
let inputUri = Uri(inputUrl)
111+
let mutable resolvedUri : Uri = Unchecked.defaultof<Uri>
112+
113+
let success = CrossLinkResolver.TryResolve(Action<_>(errorEmitter), fetchedLinks, uriResolver, inputUri, &resolvedUri)
114+
115+
if not errors.IsEmpty then
116+
failwithf $"Resolution for '%s{inputUrl}' failed with errors: %A{errors}"
117+
118+
test <@ success @>
119+
match box resolvedUri with
120+
| null -> failwithf $"Resolved URI was null for input '%s{inputUrl}' even though TryResolve returned true."
121+
| _ ->
122+
let expectedFullUrl = baseExpectedUrl + expectedPathWithOptionalAnchor
123+
test <@ resolvedUri.ToString() = expectedFullUrl @>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Licensed to Elasticsearch B.V under one or more agreements.
2+
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
3+
// See the LICENSE file in the project root for more information
4+
5+
module ``inline elements``.``complex anchors``
6+
7+
open Xunit
8+
open authoring.CrossLinkResolverAssertions
9+
10+
type ``scenario 1``() =
11+
12+
[<Fact>]
13+
let ``No anchor redirects to new-anchorless page``() =
14+
resolvesTo ("docs-content://testing/redirects/multi-topic-page-1-old.md") "/testing/redirects/multi-topic-page-1-new-anchorless"
15+
16+
[<Fact>]
17+
let ``Unmatched anchor for '!' rule redirects to new-anchorless page and drops anchor``() =
18+
resolvesTo ("docs-content://testing/redirects/multi-topic-page-1-old.md#unmatched-anchor") "/testing/redirects/multi-topic-page-1-new-anchorless"
19+
20+
[<Fact>]
21+
let ``topic-a-intro redirects to topic-a-subpage and drops anchor (null target)``() =
22+
resolvesTo ("docs-content://testing/redirects/multi-topic-page-1-old.md#topic-a-intro") "/testing/redirects/multi-topic-page-1-new-topic-a-subpage"
23+
24+
[<Fact>]
25+
let ``topic-a-details redirects to topic-a-subpage with new anchor``() =
26+
resolvesTo ("docs-content://testing/redirects/multi-topic-page-1-old.md#topic-a-details") "/testing/redirects/multi-topic-page-1-new-topic-a-subpage#details-anchor"
27+
28+
[<Fact>]
29+
let ``topic-b-main redirects to topic-b-subpage with new anchor``() =
30+
resolvesTo ("docs-content://testing/redirects/multi-topic-page-1-old.md#topic-b-main") "/testing/redirects/multi-topic-page-1-new-topic-b-subpage#main-anchor"
31+
32+
[<Fact>]
33+
let ``topic-c-main redirects to old page and keeps anchor``() =
34+
resolvesTo ("docs-content://testing/redirects/multi-topic-page-1-old.md#topic-c-main") "/testing/redirects/multi-topic-page-1-old#topic-c-main"
35+
36+
37+
type ``Scenario 2``() =
38+
39+
[<Fact>]
40+
let ``No anchor redirects to old page (self)``() =
41+
resolvesTo ("docs-content://testing/redirects/multi-topic-page-2-old.md") "/testing/redirects/multi-topic-page-2-old"
42+
43+
[<Fact>]
44+
let ``Unmatched anchor for '{}' rule redirects to old page (self) and keeps anchor``() =
45+
resolvesTo ("docs-content://testing/redirects/multi-topic-page-2-old.md#unmatched-anchor") "/testing/redirects/multi-topic-page-2-old#unmatched-anchor"
46+
47+
[<Fact>]
48+
let ``topic-a-intro redirects to topic-a-subpage with new anchor``() =
49+
resolvesTo ("docs-content://testing/redirects/multi-topic-page-2-old.md#topic-a-intro") "/testing/redirects/multi-topic-page-2-new-topic-a-subpage#introduction"
50+
51+
[<Fact>]
52+
let ``topic-a-details redirects to topic-a-subpage and drops anchor (null target)``() =
53+
resolvesTo ("docs-content://testing/redirects/multi-topic-page-2-old.md#topic-a-details") "/testing/redirects/multi-topic-page-2-new-topic-a-subpage"
54+
55+
[<Fact>]
56+
let ``topic-b-main redirects to topic-b-subpage with new anchor``() =
57+
resolvesTo ("docs-content://testing/redirects/multi-topic-page-2-old.md#topic-b-main") "/testing/redirects/multi-topic-page-2-new-topic-b-subpage#summary"
58+
59+
[<Fact>]
60+
let ``topic-b-config redirects to topic-b-subpage and drops anchor (null target)``() =
61+
resolvesTo ("docs-content://testing/redirects/multi-topic-page-2-old.md#topic-b-config") "/testing/redirects/multi-topic-page-2-new-topic-b-subpage"

tests/authoring/Inline/CrossLinks.fs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ type ``error when using wrong scheme`` () =
3838
[<Fact>]
3939
let ``error on bad scheme`` () =
4040
markdown
41-
|> hasError "'docs-x' is not declared as valid cross link repository in docset.yml under cross_links"
41+
|> hasError "'docs-x' was not found in the cross link index"
4242

4343
[<Fact>]
4444
let ``has no warning`` () = markdown |> hasNoWarnings
@@ -89,14 +89,14 @@ type ``link to repository that does not resolve yet`` () =
8989
let ``validate HTML`` () =
9090
markdown |> convertsToHtml """
9191
<p><a
92-
href="https://docs-v3-preview.elastic.dev/elastic/elasticsearch/tree/main/">
92+
href="elasticsearch:/index.md">
9393
Elasticsearch Documentation
9494
</a>
9595
</p>
9696
"""
9797

9898
[<Fact>]
99-
let ``has no errors`` () = markdown |> hasNoErrors
99+
let ``error when not found in links.json`` () = markdown |> hasError("'elasticsearch' was not found in the cross link index")
100100

101101
[<Fact>]
102102
let ``has no warning`` () = markdown |> hasNoWarnings
@@ -133,14 +133,14 @@ type ``link to repository that does not resolve yet using double slashes`` () =
133133
let ``validate HTML`` () =
134134
markdown |> convertsToHtml """
135135
<p><a
136-
href="https://docs-v3-preview.elastic.dev/elastic/elasticsearch/tree/main/">
136+
href="elasticsearch://index.md">
137137
Elasticsearch Documentation
138138
</a>
139139
</p>
140140
"""
141141

142142
[<Fact>]
143-
let ``has no errors`` () = markdown |> hasNoErrors
143+
let ``error when not found in links.json`` () = markdown |> hasError("'elasticsearch' was not found in the cross link index")
144144

145145
[<Fact>]
146146
let ``has no warning`` () = markdown |> hasNoWarnings

tests/authoring/authoring.fsproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<Compile Include="Framework\HtmlAssertions.fs"/>
3939
<Compile Include="Framework\ErrorCollectorAssertions.fs"/>
4040
<Compile Include="Framework\MarkdownDocumentAssertions.fs"/>
41+
<Compile Include="Framework\CrossLinkResolverAssertions.fs" />
4142
<Compile Include="Inline\Substitutions.fs"/>
4243
<Compile Include="Inline\InlineAnchors.fs"/>
4344
<Compile Include="Inline\Comments.fs"/>
@@ -47,6 +48,7 @@
4748
<Compile Include="Inline\CrossLinks.fs" />
4849
<Compile Include="Inline\CrossLinksRedirects.fs" />
4950
<Compile Include="Inline\RelativeLinks.fs" />
51+
<Compile Include="Inline\CrossLinkRedirectAnchors.fs" />
5052
<Compile Include="Container\DefinitionLists.fs"/>
5153
<Compile Include="Generator\LinkReferenceFile.fs"/>
5254
<Compile Include="Blocks\CodeBlocks\CodeBlocks.fs"/>

0 commit comments

Comments
 (0)