Skip to content

Commit 30e0d03

Browse files
authored
Add tests for nested and indented inline anchors (#359)
* Add tests for nested and indented inline anchors Introduced new tests to verify inline anchor generation within definition lists and indented code blocks. Ensures proper HTML output and validates parsing behavior to prevent future regressions. This removes indented code support from our parsers. The problem was that during our minimal parse phase anchors that lived in indented code (lists/definition lists) was not discovered because they were marked as part of an indented code block. Removing support for indented code blocks since all code should be included through fenced blocks. (cherry picked from commit 81d20d2) * dotnet format * remove unused debug code
1 parent a1deace commit 30e0d03

File tree

9 files changed

+200
-37
lines changed

9 files changed

+200
-37
lines changed

src/Elastic.Markdown/IO/MarkdownFile.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public MarkdownFile[] YieldParents()
8989
return parents.ToArray();
9090
}
9191

92-
public async Task<MarkdownDocument> MinimalParse(Cancel ctx)
92+
public async Task<MarkdownDocument> MinimalParseAsync(Cancel ctx)
9393
{
9494
var document = await MarkdownParser.MinimalParseAsync(SourceFile, ctx);
9595
ReadDocumentInstructions(document);
@@ -99,7 +99,7 @@ public async Task<MarkdownDocument> MinimalParse(Cancel ctx)
9999
public async Task<MarkdownDocument> ParseFullAsync(Cancel ctx)
100100
{
101101
if (!_instructionsParsed)
102-
await MinimalParse(ctx);
102+
await MinimalParseAsync(ctx);
103103

104104
var document = await MarkdownParser.ParseAsync(SourceFile, YamlFrontMatter, ctx);
105105
return document;

src/Elastic.Markdown/IO/Navigation/DocumentationGroup.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,10 @@ public async Task Resolve(Cancel ctx = default)
146146
if (_resolved)
147147
return;
148148

149-
await Parallel.ForEachAsync(FilesInOrder, ctx, async (file, token) => await file.MinimalParse(token));
149+
await Parallel.ForEachAsync(FilesInOrder, ctx, async (file, token) => await file.MinimalParseAsync(token));
150150
await Parallel.ForEachAsync(GroupsInOrder, ctx, async (group, token) => await group.Resolve(token));
151151

152-
await (Index?.MinimalParse(ctx) ?? Task.CompletedTask);
152+
await (Index?.MinimalParseAsync(ctx) ?? Task.CompletedTask);
153153

154154
_resolved = true;
155155
}

src/Elastic.Markdown/Myst/MarkdownParser.cs

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Elastic.Markdown.Myst.Substitution;
1515
using Markdig;
1616
using Markdig.Extensions.EmphasisExtras;
17+
using Markdig.Parsers;
1718
using Markdig.Syntax;
1819

1920
namespace Elastic.Markdown.Myst;
@@ -28,34 +29,59 @@ public class MarkdownParser(
2829

2930
private BuildContext Context { get; } = context;
3031

31-
public static MarkdownPipeline MinimalPipeline { get; } =
32-
new MarkdownPipelineBuilder()
33-
.UseYamlFrontMatter()
34-
.UseInlineAnchors()
35-
.UseHeadingsWithSlugs()
36-
.UseDirectives()
37-
.Build();
32+
// ReSharper disable once InconsistentNaming
33+
private static MarkdownPipeline? _minimalPipeline;
34+
public static MarkdownPipeline MinimalPipeline
35+
{
36+
get
37+
{
38+
if (_minimalPipeline is not null)
39+
return _minimalPipeline;
40+
var builder = new MarkdownPipelineBuilder()
41+
.UseYamlFrontMatter()
42+
.UseInlineAnchors()
43+
.UseHeadingsWithSlugs()
44+
.UseDirectives();
45+
46+
builder.BlockParsers.TryRemove<IndentedCodeBlockParser>();
47+
_minimalPipeline = builder.Build();
48+
return _minimalPipeline;
49+
50+
}
51+
}
3852

39-
public static MarkdownPipeline Pipeline { get; } =
40-
new MarkdownPipelineBuilder()
41-
.EnableTrackTrivia()
42-
.UseInlineAnchors()
43-
.UsePreciseSourceLocation()
44-
.UseDiagnosticLinks()
45-
.UseHeadingsWithSlugs()
46-
.UseEmphasisExtras(EmphasisExtraOptions.Default)
47-
.UseSoftlineBreakAsHardlineBreak()
48-
.UseSubstitution()
49-
.UseComments()
50-
.UseYamlFrontMatter()
51-
.UseGridTables()
52-
.UsePipeTables()
53-
.UseDirectives()
54-
.UseDefinitionLists()
55-
.UseEnhancedCodeBlocks()
56-
.DisableHtml()
57-
.UseHardBreaks()
58-
.Build();
53+
// ReSharper disable once InconsistentNaming
54+
private static MarkdownPipeline? _pipeline;
55+
public static MarkdownPipeline Pipeline
56+
{
57+
get
58+
{
59+
if (_pipeline is not null)
60+
return _pipeline;
61+
62+
var builder = new MarkdownPipelineBuilder()
63+
.EnableTrackTrivia()
64+
.UseInlineAnchors()
65+
.UsePreciseSourceLocation()
66+
.UseDiagnosticLinks()
67+
.UseHeadingsWithSlugs()
68+
.UseEmphasisExtras(EmphasisExtraOptions.Default)
69+
.UseSoftlineBreakAsHardlineBreak()
70+
.UseSubstitution()
71+
.UseComments()
72+
.UseYamlFrontMatter()
73+
.UseGridTables()
74+
.UsePipeTables()
75+
.UseDirectives()
76+
.UseDefinitionLists()
77+
.UseEnhancedCodeBlocks()
78+
.DisableHtml()
79+
.UseHardBreaks();
80+
builder.BlockParsers.TryRemove<IndentedCodeBlockParser>();
81+
_pipeline = builder.Build();
82+
return _pipeline;
83+
}
84+
}
5985

6086
public ConfigurationFile Configuration { get; } = configuration;
6187

tests/authoring/Framework/ErrorCollectorAssertions.fs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ module DiagnosticsCollectorAssertions =
1616
[<DebuggerStepThrough>]
1717
let hasNoErrors (actual: Lazy<GeneratorResults>) =
1818
let actual = actual.Value
19-
test <@ actual.Context.Collector.Errors = 0 @>
19+
let errors = actual.Context.Collector.Errors
20+
test <@ errors = 0 @>
2021

2122
[<DebuggerStepThrough>]
2223
let hasError (expected: string) (actual: Lazy<GeneratorResults>) =

tests/authoring/Framework/HtmlAssertions.fs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ open AngleSharp.Html.Parser
1414
open DiffPlex.DiffBuilder
1515
open DiffPlex.DiffBuilder.Model
1616
open JetBrains.Annotations
17+
open Swensen.Unquote
1718
open Xunit.Sdk
1819

1920
[<AutoOpen>]
@@ -87,7 +88,7 @@ actual: {actual}
8788
use sw = new StringWriter()
8889
document.Body.Children
8990
|> Seq.iter _.ToHtml(sw, PrettyMarkupFormatter())
90-
sw.ToString()
91+
sw.ToString().TrimStart('\n')
9192

9293
let private createDiff expected actual =
9394
let diffs =
@@ -110,15 +111,37 @@ actual: {actual}
110111
"""
111112
raise (XunitException(msg))
112113

114+
[<DebuggerStepThrough>]
115+
let toHtml ([<LanguageInjection("html")>]expected: string) (actual: MarkdownResult) =
116+
createDiff expected actual.Html
117+
113118
[<DebuggerStepThrough>]
114119
let convertsToHtml ([<LanguageInjection("html")>]expected: string) (actual: Lazy<GeneratorResults>) =
115120
let actual = actual.Value
116121

117122
let defaultFile = actual.MarkdownResults |> Seq.head
118-
createDiff expected defaultFile.Html
123+
defaultFile |> toHtml expected
119124

120125
[<DebuggerStepThrough>]
121-
let toHtml ([<LanguageInjection("html")>]expected: string) (actual: MarkdownResult) =
122-
createDiff expected actual.Html
126+
let containsHtml ([<LanguageInjection("html")>]expected: string) (actual: MarkdownResult) =
127+
128+
let prettyExpected = prettyHtml expected
129+
let prettyActual = prettyHtml actual.Html
130+
131+
if not <| prettyActual.Contains prettyExpected then
132+
let msg = $"""Expected html to contain:
133+
{prettyExpected}
123134
135+
But was not found in:
124136
137+
{prettyActual}
138+
"""
139+
raise (XunitException(msg))
140+
141+
142+
[<DebuggerStepThrough>]
143+
let convertsToContainingHtml ([<LanguageInjection("html")>]expected: string) (actual: Lazy<GeneratorResults>) =
144+
let actual = actual.Value
145+
146+
let defaultFile = actual.MarkdownResults |> Seq.head
147+
defaultFile |> containsHtml expected
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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.Diagnostics
8+
open Markdig.Syntax
9+
open Xunit.Sdk
10+
11+
module MarkdownDocumentAssertions =
12+
13+
[<DebuggerStepThrough>]
14+
let parses<'element when 'element :> MarkdownObject> (actual: MarkdownResult) =
15+
let unsupportedBlocks = actual.Document.Descendants<'element>() |> Array.ofSeq
16+
if unsupportedBlocks.Length = 0 then
17+
raise (XunitException($"Could not find {typedefof<'element>.Name} in fully parsed document"))
18+
unsupportedBlocks;
19+
20+
[<DebuggerStepThrough>]
21+
let parsesMinimal<'element when 'element :> MarkdownObject> (actual: MarkdownResult) =
22+
let unsupportedBlocks = actual.MinimalParse.Descendants<'element>() |> Array.ofSeq
23+
if unsupportedBlocks.Length = 0 then
24+
raise (XunitException($"Could not find {typedefof<'element>.Name} in minimally parsed document"))
25+
unsupportedBlocks;

tests/authoring/Framework/TestValues.fs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type TestLoggerFactory () =
6161

6262
type MarkdownResult = {
6363
File: MarkdownFile
64+
MinimalParse: MarkdownDocument
6465
Document: MarkdownDocument
6566
Html: string
6667
Context: MarkdownTestContext
@@ -89,8 +90,9 @@ and MarkdownTestContext =
8990
|> Seq.map (fun (f: MarkdownFile) -> task {
9091
// technically we do this work twice since generate all also does it
9192
let! document = f.ParseFullAsync(ctx)
93+
let! minimal = f.MinimalParseAsync(ctx)
9294
let html = f.CreateHtml(document)
93-
return { File = f; Document = document; Html = html; Context = this }
95+
return { File = f; Document = document; MinimalParse = minimal; Html = html; Context = this }
9496
})
9597
// this is not great code, refactor or depend on FSharp.Control.TaskSeq
9698
// for now this runs without issue

tests/authoring/Inline/InlineAnchors.fs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44

55
module ``inline elements``.``anchors DEPRECATED``
66

7+
open Elastic.Markdown.Myst.InlineParsers
8+
open Markdig.Syntax
9+
open Swensen.Unquote
10+
open System.Linq
711
open Xunit
812
open authoring
13+
open authoring.MarkdownDocumentAssertions
914

1015
type ``inline anchor in the middle`` () =
1116

@@ -22,3 +27,83 @@ this is *regular* text and this $$$is-an-inline-anchor$$$ and this continues to
2227
"""
2328
[<Fact>]
2429
let ``has no errors`` () = markdown |> hasNoErrors
30+
31+
type ``inline anchors embedded in definition lists`` () =
32+
33+
static let markdown = Setup.Generate [
34+
Index """# Testing nested inline anchors
35+
36+
$$$search-type$$$
37+
38+
`search_type`
39+
: (Optional, string) How distributed term frequencies are calculated for relevance scoring.
40+
41+
::::{dropdown} Valid values for `search_type`
42+
`query_then_fetch`
43+
: (Default) Distributed term frequencies are calculated locally for each shard running the search. We recommend this option for faster searches with potentially less accurate scoring.
44+
45+
$$$dfs-query-then-fetch$$$
46+
47+
`dfs_query_then_fetch`
48+
: Distributed term frequencies are calculated globally, using information gathered from all shards running the search.
49+
50+
::::
51+
"""
52+
Markdown "file.md" """
53+
[Link to first](index.md#search-type)
54+
[Link to second](index.md#dfs-query-then-fetch)
55+
"""
56+
]
57+
58+
[<Fact>]
59+
let ``emits nested inline anchor`` () =
60+
markdown |> convertsToContainingHtml """<a id="dfs-query-then-fetch"></a>"""
61+
62+
[<Fact>]
63+
let ``emits definition list block anchor`` () =
64+
markdown |> convertsToContainingHtml """<a id="search-type"></a>"""
65+
66+
[<Fact>]
67+
let ``has no errors`` () = markdown |> hasNoErrors
68+
69+
[<Fact>]
70+
let ``minimal parse sees two inline anchors`` () =
71+
let inlineAnchors = markdown |> converts "index.md" |> parsesMinimal<InlineAnchor>
72+
test <@ inlineAnchors.Length = 2 @>
73+
74+
75+
76+
type ``inline anchors embedded in indented code`` () =
77+
78+
static let markdown = Setup.Generate [
79+
Index """# Testing nested inline anchors
80+
81+
$$$search-type$$$
82+
83+
indented codeblock
84+
85+
$$$dfs-query-then-fetch$$$
86+
87+
block
88+
"""
89+
Markdown "file.md" """
90+
[Link to first](index.md#search-type)
91+
[Link to second](index.md#dfs-query-then-fetch)
92+
"""
93+
]
94+
95+
[<Fact>]
96+
let ``emits nested inline anchor`` () =
97+
markdown |> convertsToContainingHtml """<a id="dfs-query-then-fetch"></a>"""
98+
99+
[<Fact>]
100+
let ``emits definition list block anchor`` () =
101+
markdown |> convertsToContainingHtml """<a id="search-type"></a>"""
102+
103+
[<Fact>]
104+
let ``has no errors`` () = markdown |> hasNoErrors
105+
106+
[<Fact>]
107+
let ``minimal parse sees two inline anchors`` () =
108+
let inlineAnchors = markdown |> converts "index.md" |> parsesMinimal<InlineAnchor>
109+
test <@ inlineAnchors.Length = 2 @>

tests/authoring/authoring.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<Compile Include="Framework\MarkdownResultsAssertions.fs" />
3030
<Compile Include="Framework\HtmlAssertions.fs" />
3131
<Compile Include="Framework\ErrorCollectorAssertions.fs" />
32+
<Compile Include="Framework\MarkdownDocumentAssertions.fs" />
3233
<Compile Include="Inline\Substitutions.fs" />
3334
</ItemGroup>
3435

0 commit comments

Comments
 (0)