From 62867c6e28548157ab8c60318591d3e26838dda8 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 12:26:24 +0100 Subject: [PATCH 01/18] Wire Nullean.Snapshots into every test project The ApiExplorer pilot (#4654) used Nullean.Snapshots.Html and .TUnit 0.2.0 in one project. This makes the library available to all unit and integration test projects at 0.4.0 and adds the shared pieces the next migration steps build on: - tests-shared/Elastic.Documentation.Testing: DocsHtml (page section without the fixture H1 and htmx attributes), DiagnosticsSnapshot, NavigationDump, MockFileSystemDump. A scrubber for temp and repo paths is compiled into each test project. - The snap CLI as a local tool; CI uploads received snapshots when a test run fails; the build fails on orphaned snapshots after a unit run. - AGENTS.md describes the workflow. The PR review prompt no longer claims the repo uses xUnit. The five TestDiagnosticsCollector copies stay: they differ in lifecycle (Write vs HandleItem), so merging them is a behaviour change. DiagnosticsSnapshot works on any of them. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/ci.yml | 9 +++ .github/workflows/pr-review.yml | 2 +- AGENTS.md | 6 ++ Directory.Packages.props | 8 ++- build/Targets.fs | 6 ++ docs-builder.slnx | 4 ++ dotnet-tools.json | 7 ++ tests-integration/Directory.Build.props | 7 ++ tests-shared/Directory.Build.props | 11 +++ .../DiagnosticsSnapshot.cs | 32 +++++++++ .../Elastic.Documentation.Testing/DocsHtml.cs | 34 +++++++++ .../Elastic.Documentation.Testing.csproj | 28 ++++++++ .../Linked/SnapshotScrubbing.cs | 39 +++++++++++ .../MockFileSystemDump.cs | 35 ++++++++++ .../NavigationDump.cs | 51 ++++++++++++++ tests/Directory.Build.props | 7 ++ .../Elastic.ApiExplorer.Tests.csproj | 2 - .../Testing/SnapshotHelpersTests.cs | 70 +++++++++++++++++++ 18 files changed, 353 insertions(+), 5 deletions(-) create mode 100644 tests-shared/Directory.Build.props create mode 100644 tests-shared/Elastic.Documentation.Testing/DiagnosticsSnapshot.cs create mode 100644 tests-shared/Elastic.Documentation.Testing/DocsHtml.cs create mode 100644 tests-shared/Elastic.Documentation.Testing/Elastic.Documentation.Testing.csproj create mode 100644 tests-shared/Elastic.Documentation.Testing/Linked/SnapshotScrubbing.cs create mode 100644 tests-shared/Elastic.Documentation.Testing/MockFileSystemDump.cs create mode 100644 tests-shared/Elastic.Documentation.Testing/NavigationDump.cs create mode 100644 tests/Elastic.Documentation.Tests/Testing/SnapshotHelpersTests.cs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 50c34ac2c2..7d4066e86c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -203,6 +203,15 @@ jobs: # --single-target skips the compile dependency; the Compile step above already built everything run: dotnet run --project build -c release -- unit-test --single-target + - name: Upload received snapshots + # A failed snapshot test leaves its received file here. Download it into .artifacts/.snapshots/received/ and run `dotnet snap review`. + if: ${{ failure() && needs.changes.outputs.code == 'true' }} + uses: actions/upload-artifact@v7 + with: + name: received-snapshots-${{ matrix.os }} + path: .artifacts/.snapshots/received/ + if-no-files-found: ignore + - name: Publish AOT if: ${{ needs.changes.outputs.code == 'true' && (github.event_name == 'push' && matrix.os != 'ubuntu-latest') }} # publish containers already validates AOT build run: dotnet run --project build -c release -- publishbinaries diff --git a/.github/workflows/pr-review.yml b/.github/workflows/pr-review.yml index 3c884acdc3..314cf54941 100644 --- a/.github/workflows/pr-review.yml +++ b/.github/workflows/pr-review.yml @@ -27,7 +27,7 @@ jobs: validates cross-repo references, and ships as native AOT binaries for CI. ## Language and frameworks - - C# (.NET) with xUnit v3 + AwesomeAssertions for tests. Do NOT suggest TUnit APIs — the migration hasn't happened yet. + - C# (.NET) with TUnit + AwesomeAssertions for tests. Expected output lives in Nullean.Snapshots snapshots (inline literals and `.snapshots/` files); never suggest accepting snapshots in code or CI. - TypeScript/React frontend under src/Elastic.Documentation.Site/. - Test naming convention: Method_Scenario_Expected. diff --git a/AGENTS.md b/AGENTS.md index 0f31e021e8..feb01f4633 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -65,6 +65,12 @@ Tests live in `tests/` (unit) and `tests-integration/` (integration). - **C#**: TUnit · AwesomeAssertions · FakeItEasy across all test projects. All test projects (unit and integration) use TUnit (wired centrally via `tests/Directory.Build.props` and `tests-integration/Directory.Build.props`; no per-project flag needed). Write new tests using TUnit APIs: `[Test]`, `[Arguments(...)]`, `[MethodDataSource(nameof(X))]`. - **TypeScript**: Vitest +- **Snapshots** (`Nullean.Snapshots`, wired for every test project in `tests/Directory.Build.props`; helpers in `tests-shared/Elastic.Documentation.Testing`): expected output lives in snapshots, not in chains of `Contain` assertions. + - Small values go inline: `x.Should().MatchInlineSnapshot("""...""")`, `MatchInlineHtmlSnapshot`, `MatchInlineJsonSnapshot`. Leave the argument out to write a new one. Large values go to a verified file: `MatchSnapshot()`, `MatchHtmlSnapshot()`, `MatchJsonSnapshot()`, `MatchYamlSnapshot()`. + - Diagnostics: `collector.Diagnostics.ToSnapshotText()` (`DiagnosticsSnapshot`). A clean run is the empty snapshot. + - Navigation trees and generated files: `NavigationDump.ToText(...)`, `MockFileSystemDump.ToText(fs, dir)`. Rendered docs HTML: `DocsHtml.Section()` (the page content, no fixture H1, no htmx attributes) or `DocsHtml.Body()`. + - Accept with the `snap` tool (`dotnet snap status`, `dotnet snap review`, `dotnet snap accept `), or `SNAPSHOT_ACCEPT=1`. Read the diff of the changed `.verified.*` files and inline literals before committing. Nothing is accepted in CI. When a CI test run fails, download the `received-snapshots-*` artifact into `.artifacts/.snapshots/received/` and run `dotnet snap review`. + - Changing a snapshot test's output is a behaviour change. Mutate the code once and confirm the test fails before you commit. - **HTML snapshots** (`Nullean.Snapshots.Html`, used in `Elastic.ApiExplorer.Tests`): when a test would check one rendered block with many `Contain` assertions, compare it to a verified snapshot instead and keep one assertion that states the intent. - Write `html.Should().MatchHtmlSnapshot()`. Narrow it with `new HtmlSnapshotOptions().Select("#some-id")` or `.RemoveAttributes(...)` so unrelated markup and volatile values (bundle names, CI git info) stay out. - Snapshot a partial or one section, not a whole page. Whole-page snapshots break on unrelated changes. diff --git a/Directory.Packages.props b/Directory.Packages.props index 782c52aab0..b1dcf0d2c0 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -127,8 +127,12 @@ - - + + + + + + diff --git a/build/Targets.fs b/build/Targets.fs index a73833190f..432a81e056 100644 --- a/build/Targets.fs +++ b/build/Targets.fs @@ -205,6 +205,12 @@ let private runTests (testSuite: TestSuite) (parsed: ParseResults) = |> Array.toList if not (List.isEmpty failures) then failwithf "Test projects failed:\n%s" (String.concat "\n" failures) + + // Every test project has now run, so the run manifests are complete: a verified snapshot nobody used belongs to a deleted or renamed test. + if testSuite <> Integration then + match exec { exit_code_of "dotnet" "snap" "orphans" } with + | 0 -> () + | _ -> failwith "Orphaned snapshots found. Delete them with `dotnet snap orphans --delete` and commit the removal." let private compressibleExtensions = set [".html"; ".css"; ".js"; ".json"; ".svg"; ".xml"; ".txt"] diff --git a/docs-builder.slnx b/docs-builder.slnx index 47468e9ede..31e5d66687 100644 --- a/docs-builder.slnx +++ b/docs-builder.slnx @@ -90,6 +90,10 @@ + + + + diff --git a/dotnet-tools.json b/dotnet-tools.json index 084330e010..d77fdfb621 100644 --- a/dotnet-tools.json +++ b/dotnet-tools.json @@ -43,6 +43,13 @@ "curb" ], "rollForward": false + }, + "nullean.snapshots.cli": { + "version": "0.4.0", + "commands": [ + "snap" + ], + "rollForward": false } } } diff --git a/tests-integration/Directory.Build.props b/tests-integration/Directory.Build.props index 379438ea3e..1717682543 100644 --- a/tests-integration/Directory.Build.props +++ b/tests-integration/Directory.Build.props @@ -18,6 +18,13 @@ + + + + + + + diff --git a/tests-shared/Directory.Build.props b/tests-shared/Directory.Build.props new file mode 100644 index 0000000000..5b92d69384 --- /dev/null +++ b/tests-shared/Directory.Build.props @@ -0,0 +1,11 @@ + + + + + + CA1822;CA1707;IDE0058 + false + false + + diff --git a/tests-shared/Elastic.Documentation.Testing/DiagnosticsSnapshot.cs b/tests-shared/Elastic.Documentation.Testing/DiagnosticsSnapshot.cs new file mode 100644 index 0000000000..0e8f94f1f2 --- /dev/null +++ b/tests-shared/Elastic.Documentation.Testing/DiagnosticsSnapshot.cs @@ -0,0 +1,32 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Text; +using Elastic.Documentation.Diagnostics; + +namespace Elastic.Documentation.Testing; + +public static class DiagnosticsSnapshot +{ + /// + /// One line per diagnostic, error|warning|hint file:line message, in the order they were written. Empty when + /// there are none, so a clean run reads as MatchInlineSnapshot(""). + /// + public static string ToSnapshotText(this IEnumerable diagnostics) + { + var sb = new StringBuilder(); + foreach (var d in diagnostics) + { + var severity = d.Severity switch + { + Severity.Error => "error", + Severity.Warning => "warning", + _ => "hint" + }; + var position = d.Line is { } line ? $"{d.File}:{line}" : d.File; + sb.Append(severity).Append(' ').Append(position).Append(' ').Append(d.Message).Append('\n'); + } + return sb.ToString(); + } +} diff --git a/tests-shared/Elastic.Documentation.Testing/DocsHtml.cs b/tests-shared/Elastic.Documentation.Testing/DocsHtml.cs new file mode 100644 index 0000000000..961b026997 --- /dev/null +++ b/tests-shared/Elastic.Documentation.Testing/DocsHtml.cs @@ -0,0 +1,34 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using Nullean.Snapshots.Html; + +namespace Elastic.Documentation.Testing; + +/// +/// How docs-builder's tests normalise rendered HTML before comparing or snapshotting it. One place for what +/// Elastic.Markdown.Tests.PrettyHtmlExtensions and Elastic.Authoring.Tests.Framework.HtmlAssertions +/// used to do separately. +/// +public static class DocsHtml +{ + /// htmx navigation attributes on links are plumbing, not content. + private static readonly string[] HtmxAttributes = ["hx-get", "hx-select-oob", "hx-swap", "hx-indicator", "hx-push-url", "preload"]; + + /// The whole body, with htmx attributes removed from links. + public static HtmlSnapshotOptions Body() => new HtmlSnapshotOptions().RemoveAttributesFrom("a", HtmxAttributes); + + /// + /// The rendered page's content section, without the leading H1 the test fixtures wrap every document in + /// (# Test Document), and with htmx attributes removed from links. + /// + public static HtmlSnapshotOptions Section() => + Body() + .Select("section#elastic-docs-v3") + .Transform(root => + { + if (root.FirstElementChild is { TagName: "H1" } h1) + h1.Remove(); + }); +} diff --git a/tests-shared/Elastic.Documentation.Testing/Elastic.Documentation.Testing.csproj b/tests-shared/Elastic.Documentation.Testing/Elastic.Documentation.Testing.csproj new file mode 100644 index 0000000000..ab56af73cd --- /dev/null +++ b/tests-shared/Elastic.Documentation.Testing/Elastic.Documentation.Testing.csproj @@ -0,0 +1,28 @@ + + + + net10.0 + Elastic.Documentation.Testing + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests-shared/Elastic.Documentation.Testing/Linked/SnapshotScrubbing.cs b/tests-shared/Elastic.Documentation.Testing/Linked/SnapshotScrubbing.cs new file mode 100644 index 0000000000..6a80bb71fc --- /dev/null +++ b/tests-shared/Elastic.Documentation.Testing/Linked/SnapshotScrubbing.cs @@ -0,0 +1,39 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using Nullean.Snapshots; + +namespace Elastic.Documentation.Testing.Linked; + +/// +/// Scrubbers every snapshot in docs-builder's tests gets: machine-specific paths and git SHAs never belong in a +/// verified file. Runs when any test project referencing this assembly loads it. +/// +internal static class SnapshotScrubbing +{ +#pragma warning disable CA2255 // compiled into each test project, which is application code + [ModuleInitializer] +#pragma warning restore CA2255 + internal static void Register() + { + SnapshotDefaults.AddScrubber( + Scrubbers.Regex( + new Regex(Regex.Escape(Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar)) + @"[^\s""'<>]*", RegexOptions.Compiled), + "{temp}" + ) + ); + if (FindSolutionRoot() is { } root) + SnapshotDefaults.AddScrubber(Scrubbers.Regex(new Regex(Regex.Escape(root), RegexOptions.Compiled), "{repo}")); + } + + private static string? FindSolutionRoot() + { + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "docs-builder.slnx"))) + dir = dir.Parent; + return dir?.FullName.TrimEnd(Path.DirectorySeparatorChar); + } +} diff --git a/tests-shared/Elastic.Documentation.Testing/MockFileSystemDump.cs b/tests-shared/Elastic.Documentation.Testing/MockFileSystemDump.cs new file mode 100644 index 0000000000..daaf2d8ed6 --- /dev/null +++ b/tests-shared/Elastic.Documentation.Testing/MockFileSystemDump.cs @@ -0,0 +1,35 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.IO.Abstractions; +using System.Text; + +namespace Elastic.Documentation.Testing; + +public static class MockFileSystemDump +{ + /// + /// Every file under , sorted by path, each under an === relative/path === + /// header. Meant for tests that read generated files back from a mock file system. + /// + public static string ToText(IFileSystem fileSystem, string directory, string searchPattern = "*") + { + var root = fileSystem.Path.GetFullPath(directory); + var sb = new StringBuilder(); + var files = fileSystem + .Directory + .EnumerateFiles(root, searchPattern, SearchOption.AllDirectories) + .Select(f => (Full: f, Relative: fileSystem.Path.GetRelativePath(root, f).Replace('\\', '/'))) + .OrderBy(f => f.Relative, StringComparer.Ordinal); + foreach (var (full, relative) in files) + { + sb.Append("=== ").Append(relative).Append(" ===\n"); + var content = fileSystem.File.ReadAllText(full).Replace("\r\n", "\n"); + sb.Append(content); + if (!content.EndsWith('\n')) + sb.Append('\n'); + } + return sb.ToString(); + } +} diff --git a/tests-shared/Elastic.Documentation.Testing/NavigationDump.cs b/tests-shared/Elastic.Documentation.Testing/NavigationDump.cs new file mode 100644 index 0000000000..a70dfa66f6 --- /dev/null +++ b/tests-shared/Elastic.Documentation.Testing/NavigationDump.cs @@ -0,0 +1,51 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.Text; +using Elastic.Documentation.Navigation; + +namespace Elastic.Documentation.Testing; + +public static class NavigationDump +{ + /// + /// The navigation tree as indented text, one node per line: Type url "title", with [hidden] and + /// [island] flags. A node lists its index page first (index: Type url "title"), then its children. The + /// index page is where most of the interesting URLs live. A child whose Parent is not the node it is listed + /// under is flagged [wrong parent]; index pages are not checked, because a virtual file's index has a + /// different parent. + /// + public static string ToText(INavigationItem root) + { + var sb = new StringBuilder(); + Write(sb, root, 0, null, null); + return sb.ToString(); + } + + private static void Write(StringBuilder sb, INavigationItem item, int depth, string? label, INavigationItem? container) + { + sb + .Append(' ', depth * 2) + .Append(label) + .Append(item.GetType().Name.Split('`')[0]) + .Append(" ") + .Append(item.Url) + .Append(" \"") + .Append(item.NavigationTitle) + .Append('"'); + if (item.Hidden) + sb.Append(" [hidden]"); + if (item.IsIsland) + sb.Append(" [island]"); + if (container is not null && !ReferenceEquals(item.Parent, container)) + sb.Append(" [wrong parent]"); + sb.Append('\n'); + + if (item is not INodeNavigationItem node) + return; + Write(sb, node.Index, depth + 1, "index: ", null); + foreach (var child in node.NavigationItems) + Write(sb, child, depth + 1, null, item); + } +} diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 6ce509c511..b3667da5e1 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -27,5 +27,12 @@ + + + + + + + diff --git a/tests/Elastic.ApiExplorer.Tests/Elastic.ApiExplorer.Tests.csproj b/tests/Elastic.ApiExplorer.Tests/Elastic.ApiExplorer.Tests.csproj index b6138fd950..17b5bb72f8 100644 --- a/tests/Elastic.ApiExplorer.Tests/Elastic.ApiExplorer.Tests.csproj +++ b/tests/Elastic.ApiExplorer.Tests/Elastic.ApiExplorer.Tests.csproj @@ -12,8 +12,6 @@ - - diff --git a/tests/Elastic.Documentation.Tests/Testing/SnapshotHelpersTests.cs b/tests/Elastic.Documentation.Tests/Testing/SnapshotHelpersTests.cs new file mode 100644 index 0000000000..5897c67b4a --- /dev/null +++ b/tests/Elastic.Documentation.Tests/Testing/SnapshotHelpersTests.cs @@ -0,0 +1,70 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System.IO.Abstractions.TestingHelpers; +using AwesomeAssertions; +using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Testing; +using Nullean.Snapshots; +using Nullean.Snapshots.Html; + +namespace Elastic.Documentation.Tests.Testing; + +public class SnapshotHelpersTests +{ + [Test] + public void ToSnapshotText_NoDiagnostics_IsEmpty() => new List().ToSnapshotText().Should().MatchInlineSnapshot(""); + + [Test] + public void ToSnapshotText_MixedSeverities_OneLineEachInOrder() + { + var diagnostics = new List + { + new() { Severity = Severity.Error, File = "a.md", Line = 3, Message = "bad link" }, + new() { Severity = Severity.Warning, File = "b.md", Message = "no line" }, + new() { Severity = Severity.Hint, File = "c.md", Line = 1, Message = "consider" } + }; + diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + """ + error a.md:3 bad link + warning b.md no line + hint c.md:1 consider + """ + ); + } + + [Test] + public void MockFileSystemDump_SortsByPathAndNormalisesLineEndings() + { + var fs = new MockFileSystem(); + fs.AddFile("/out/b/two.md", new MockFileData("two\r\nlines")); + fs.AddFile("/out/a.md", new MockFileData("one\n")); + MockFileSystemDump + .ToText(fs, "/out") + .Should() + .MatchInlineSnapshot( + """ + === a.md === + one + === b/two.md === + two + lines + """ + ); + } + + [Test] + public void DocsHtmlSection_DropsFixtureHeadingAndHtmxAttributes() => + """ +

Test Document

x

nav
+ """.Should().MatchInlineHtmlSnapshot( + """ +

x

+ """, + DocsHtml.Section() + ); +} From a9fd42330390df7a7c5f9ced0b4102e1d1f7f9fa Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 12:29:06 +0100 Subject: [PATCH 02/18] Compare rendered HTML with Nullean.Snapshots.Html ShouldBeHtml and ShouldContainHtml (Elastic.Markdown.Tests) and ConvertsToHtml, ConvertsToContainingHtml and the per-file ToHtml and ContainsHtml (Elastic.Authoring.Tests) keep their names and call sites, and now run on BeHtml and ContainHtml with the shared DocsHtml options. That removes the second copy of the AngleSharp.Diffing diff code and the AngleSharp.Diffing package. DiffPlex stays in Authoring for the plain text assertions. One test passed without testing anything. "marks exactly two options as a fork" gave the bare text `is-fork` to ConvertsToContainingHtml. The old normaliser printed only element children, so a text-only fragment became an empty string and every page contains an empty string. It now uses ConvertsToContainingRawHtml, like the test above it. Checked by corrupting one ShouldContainHtml and one ConvertsToHtml expectation: both fail. Co-Authored-By: Claude Sonnet 5.5 --- Directory.Packages.props | 1 - .../Blocks/Hub/GetStarted.cs | 2 +- .../Elastic.Authoring.Tests.csproj | 1 - .../Framework/HtmlAssertions.cs | 177 ++---------------- .../Elastic.Markdown.Tests.csproj | 2 - .../PrettyHtmlExtensions.cs | 127 +------------ 6 files changed, 22 insertions(+), 288 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index b1dcf0d2c0..0fdb1d5ee6 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -126,7 +126,6 @@
- diff --git a/tests/Elastic.Authoring.Tests/Blocks/Hub/GetStarted.cs b/tests/Elastic.Authoring.Tests/Blocks/Hub/GetStarted.cs index dec67a93fb..d0ccf282e0 100644 --- a/tests/Elastic.Authoring.Tests/Blocks/Hub/GetStarted.cs +++ b/tests/Elastic.Authoring.Tests/Blocks/Hub/GetStarted.cs @@ -164,7 +164,7 @@ public async Task PutsTheFirstStepInItsOwnBox() => await Docs.ConvertsToContainingRawHtml("""
  • """); [Test, DisplayName("marks exactly two options as a fork")] - public async Task MarksExactlyTwoOptionsAsAFork() => await Docs.ConvertsToContainingHtml("""is-fork"""); + public async Task MarksExactlyTwoOptionsAsAFork() => await Docs.ConvertsToContainingRawHtml("""is-fork"""); [Test, DisplayName("has no errors")] public async Task HasNoErrors() => await Docs.HasNoErrors(); diff --git a/tests/Elastic.Authoring.Tests/Elastic.Authoring.Tests.csproj b/tests/Elastic.Authoring.Tests/Elastic.Authoring.Tests.csproj index ee73b53ebd..4e5e05627f 100644 --- a/tests/Elastic.Authoring.Tests/Elastic.Authoring.Tests.csproj +++ b/tests/Elastic.Authoring.Tests/Elastic.Authoring.Tests.csproj @@ -5,7 +5,6 @@ - diff --git a/tests/Elastic.Authoring.Tests/Framework/HtmlAssertions.cs b/tests/Elastic.Authoring.Tests/Framework/HtmlAssertions.cs index 2bf599f62b..7fbebbdf03 100644 --- a/tests/Elastic.Authoring.Tests/Framework/HtmlAssertions.cs +++ b/tests/Elastic.Authoring.Tests/Framework/HtmlAssertions.cs @@ -3,13 +3,12 @@ // See the LICENSE file in the project root for more information using System.Diagnostics; -using AngleSharp.Diffing; -using AngleSharp.Diffing.Core; -using AngleSharp.Html; -using AngleSharp.Html.Parser; +using AwesomeAssertions; using DiffPlex.DiffBuilder; using DiffPlex.DiffBuilder.Model; +using Elastic.Documentation.Testing; using JetBrains.Annotations; +using Nullean.Snapshots.Html; namespace Elastic.Authoring.Tests.Framework; @@ -27,8 +26,7 @@ namespace Elastic.Authoring.Tests.Framework; /// would appear verbatim in every assertion snapshot otherwise. /// /// -/// Produces a humanized diff via that names the diff type -/// (NodeDiff / AttrDiff / MissingNodeDiff etc.) and shows expected vs. actual node text. +/// Compares through Nullean.Snapshots.Html, which reports a structural diff and then a line diff. /// /// /// All assertion methods are synchronous — the async wrapper lives on . @@ -73,173 +71,28 @@ internal static string Diff(string expected, string actual) } // ──────────────────────────────────────────────────────────────────────────────── - // AngleSharp deep HTML diff + // HTML comparison on Nullean.Snapshots.Html // ──────────────────────────────────────────────────────────────────────────────── - internal static string HtmlDiffString(IEnumerable diffs) - { - var formatter = new PrettyMarkupFormatter(); - - string NodeText(ComparisonSource source) - { - using var sw = new StringWriter(); - source.Node.ToHtml(sw, formatter); - return sw.ToString(); - } - - string AttrText(AttributeComparisonSource source) - { - using var sw = new StringWriter(); - source.Attribute.ToHtml(sw, formatter); - return sw.ToString(); - } - - // NodeDiffText and AttrDiffText accept non-nullable (callers pass .Control / .Test directly). - // For Missing*/Unexpected* cases we pass a sentinel "missing" string instead of calling these helpers. - string NodeDiffText(ComparisonSource control, ComparisonSource test) => - $"\nexpected: {NodeText(control)}\nactual: {NodeText(test)}\n"; - - string MissingNodeText(ComparisonSource control) => $"\nexpected: {NodeText(control)}\nactual: missing\n"; - - string UnexpectedNodeText(ComparisonSource test) => $"\nexpected: missing\nactual: {NodeText(test)}\n"; - - string AttrDiffText(AttributeComparisonSource control, AttributeComparisonSource test) => - $"\nexpected: {AttrText(control)}\nactual: {AttrText(test)}\n"; - - string MissingAttrText(AttributeComparisonSource control) => $"\nexpected: {AttrText(control)}\nactual: missing\n"; - - string UnexpectedAttrText(AttributeComparisonSource test) => $"\nexpected: missing\nactual: {AttrText(test)}\n"; - - static string NodeName(ComparisonSource source) => source.Node.NodeType.ToString().ToLowerInvariant(); - - return string.Join('\n', diffs.Select(d => - { - var label = d switch - { - NodeDiff nd when nd.Target == DiffTarget.Text && nd.Control.Path.Equals(nd.Test.Path, StringComparison.Ordinal) => - $"The text in {nd.Control.Path} is different.", - NodeDiff nd when nd.Target == DiffTarget.Text => - $"The expected {NodeName(nd.Control)} at {nd.Control.Path} and the actual {NodeName(nd.Test)} at {nd.Test.Path} is different.", - NodeDiff nd when nd.Control.Path.Equals(nd.Test.Path, StringComparison.Ordinal) => - $"The {NodeName(nd.Control)}s at {nd.Control.Path} are different.", - NodeDiff nd => - $"The expected {NodeName(nd.Control)} at {nd.Control.Path} and the actual {NodeName(nd.Test)} at {nd.Test.Path} are different.", - AttrDiff ad when ad.Control.Path.Equals(ad.Test.Path, StringComparison.Ordinal) => - $"The values of the attributes at {ad.Control.Path} are different.", - AttrDiff ad => $"The value of the attribute {ad.Control.Path} and actual attribute {ad.Test.Path} are different.", - MissingNodeDiff md => $"The {NodeName(md.Control)} at {md.Control.Path} is missing.", - MissingAttrDiff ma => $"The attribute at {ma.Control.Path} is missing.", - UnexpectedNodeDiff ud => $"The {NodeName(ud.Test)} at {ud.Test.Path} was not expected.", - UnexpectedAttrDiff ua => $"The attribute at {ua.Test.Path} was not expected.", - _ => throw new InvalidOperationException($"Unknown diff type detected: {d.GetType()}") - }; - - var detail = d switch - { - NodeDiff nd => NodeDiffText(nd.Control, nd.Test), - AttrDiff ad => AttrDiffText(ad.Control, ad.Test), - MissingNodeDiff md => MissingNodeText(md.Control), - MissingAttrDiff ma => MissingAttrText(ma.Control), - UnexpectedNodeDiff ud => UnexpectedNodeText(ud.Test), - UnexpectedAttrDiff ua => UnexpectedAttrText(ua.Test), - _ => throw new InvalidOperationException($"Unknown diff type detected: {d.GetType()}") - }; - - return label + detail; - })); - } - - // ──────────────────────────────────────────────────────────────────────────────── - // Pretty printer — matches the F# prettyHtml behaviour exactly - // ──────────────────────────────────────────────────────────────────────────────── - - private static string PrettyHtml(string html, string? querySelector) - { - var parser = new HtmlParser(); - var document = parser.ParseDocument(html); - var element = querySelector is null ? document.Body! : document.QuerySelector(querySelector)!; - - // Strip HTMX attributes from links — they are navigation artifacts, not content. - foreach (var link in element.QuerySelectorAll("a")) - { - link.RemoveAttribute("hx-get"); - link.RemoveAttribute("hx-select-oob"); - link.RemoveAttribute("hx-swap"); - link.RemoveAttribute("hx-indicator"); - link.RemoveAttribute("hx-push-url"); - link.RemoveAttribute("preload"); - } - - var formatter = new PrettyMarkupFormatter(); - using var sw = new StringWriter(); - - // Skip the first child if it is an H1 — the fixture wraps Setup.Markdown content in one. - var children = element.Children.ToList(); - for (var i = 0; i < children.Count; i++) - { - if (i == 0 && children[i].TagName == "H1") - continue; - children[i].ToHtml(sw, formatter); - } - - return sw.ToString().TrimStart('\n'); - } - - private static void CreateDiff(string expected, string actual) - { - // Identical pretty-printed HTML has no structural differences, so skip the AngleSharp comparison for the usual passing case. - if (string.Equals(expected, actual, StringComparison.Ordinal)) - return; - - var diffs = DiffBuilder.Compare(expected).WithTest(actual).Build(); - - var deepComparison = HtmlDiffString(diffs); - if (string.IsNullOrEmpty(deepComparison)) - return; - - var textDiff = Diff(expected, actual); - throw new AwesomeAssertions.Execution.AssertionFailedException( - $""" -Html was not equal --- DIFF -- -{textDiff} + /// + /// The rendered page's content section, minus the fixture's leading H1 and the htmx attributes on links. + /// The expected fragment is wrapped in the same section so both sides go through identical normalisation. + /// + private static HtmlSnapshotOptions Options => DocsHtml.Section(); --- Comparison -- -{deepComparison} -""" - ); - } + private static string InSection(string fragment) => $"
    {fragment}
    "; // ──────────────────────────────────────────────────────────────────────────────── // Per-file assertions (called from MarkdownResultTask / Scenario) // ──────────────────────────────────────────────────────────────────────────────── [DebuggerStepThrough] - internal static void ToHtml([LanguageInjection("html")] string expected, MarkdownResult actual) - { - var expectedHtml = PrettyHtml(expected, null); - var actualHtml = PrettyHtml(actual.Html, "section#elastic-docs-v3"); - CreateDiff(expectedHtml, actualHtml); - } + internal static void ToHtml([LanguageInjection("html")] string expected, MarkdownResult actual) => + actual.Html.Should().BeHtml(InSection(expected), Options); [DebuggerStepThrough] - internal static void ContainsHtml([LanguageInjection("html")] string expected, MarkdownResult actual) - { - var prettyExpected = PrettyHtml(expected, null); - var prettyActual = PrettyHtml(actual.Html, "section#elastic-docs-v3"); - - if (!prettyActual.Contains(prettyExpected, StringComparison.Ordinal)) - throw new AwesomeAssertions.Execution.AssertionFailedException( - $""" -Expected html to contain: -{prettyExpected} - -But was not found in: - -{prettyActual} -""" - ); - } + internal static void ContainsHtml([LanguageInjection("html")] string expected, MarkdownResult actual) => + actual.Html.Should().ContainHtml(InSection(expected), Options); [DebuggerStepThrough] internal static void ContainsRawHtml(string expected, MarkdownResult actual) diff --git a/tests/Elastic.Markdown.Tests/Elastic.Markdown.Tests.csproj b/tests/Elastic.Markdown.Tests/Elastic.Markdown.Tests.csproj index a128c14af7..e791376c2a 100644 --- a/tests/Elastic.Markdown.Tests/Elastic.Markdown.Tests.csproj +++ b/tests/Elastic.Markdown.Tests/Elastic.Markdown.Tests.csproj @@ -5,8 +5,6 @@ - - diff --git a/tests/Elastic.Markdown.Tests/PrettyHtmlExtensions.cs b/tests/Elastic.Markdown.Tests/PrettyHtmlExtensions.cs index eb552208b7..29cd3b7467 100644 --- a/tests/Elastic.Markdown.Tests/PrettyHtmlExtensions.cs +++ b/tests/Elastic.Markdown.Tests/PrettyHtmlExtensions.cs @@ -2,142 +2,27 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information -using AngleSharp.Diffing; -using AngleSharp.Html; -using AngleSharp.Html.Parser; using AwesomeAssertions; -using AwesomeAssertions.Execution; -using DiffPlex.DiffBuilder; -using DiffPlex.DiffBuilder.Model; +using Elastic.Documentation.Testing; using JetBrains.Annotations; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests; +/// HTML comparisons on Nullean.Snapshots.Html: structural, so attribute order and whitespace never fail a test. public static class PrettyHtmlExtensions { - public static string PrettyHtml([LanguageInjection("html")] this string html, bool sanitize = true) - { - var parser = new HtmlParser(); - - var document = parser.ParseDocument(html); - var element = document.Body; - if (element is null) - return string.Empty; - - if (sanitize) - { - var links = element.QuerySelectorAll("a"); - foreach (var l in links) - { - l.RemoveAttribute("hx-get"); - l.RemoveAttribute("hx-select-oob"); - l.RemoveAttribute("hx-swap"); - l.RemoveAttribute("hx-indicator"); - l.RemoveAttribute("hx-push-url"); - l.RemoveAttribute("preload"); - } - } - - using var sw = new StringWriter(); - var formatter = new PrettyMarkupFormatter(); - foreach (var c in element.Children) - { - // ReSharper disable once AccessToDisposedClosure - c.ToHtml(sw, formatter); - } - return sw.ToString().TrimStart('\n'); - } + private static HtmlSnapshotOptions Options(bool sanitize) => sanitize ? DocsHtml.Body() : new HtmlSnapshotOptions(); public static void ShouldBeHtml( [LanguageInjection("html")] this string actual, [LanguageInjection("html")] string expected, bool sanitize = true - ) - { - expected = expected.Trim('\n').PrettyHtml(sanitize); - actual = actual.Trim('\n').PrettyHtml(sanitize); + ) => actual.Trim('\n').Should().BeHtml(expected.Trim('\n'), Options(sanitize)); - var diff = DiffBuilder.Compare(actual).WithTest(expected).Build().ToArray(); - - if (diff.Length == 0) - return; - - throw new AssertionFailedException(CreateDiff(actual, expected, sanitize)); - } public static void ShouldContainHtml( [LanguageInjection("html")] this string actual, [LanguageInjection("html")] string expected, bool sanitize = true - ) - { - expected = expected.Trim('\n').PrettyHtml(sanitize); - actual = actual.Trim('\n').PrettyHtml(sanitize); - - var actualCompare = actual.Replace("\t", string.Empty); - var expectedCompare = expected.Replace("\t", string.Empty); - - // we compare over unindented HTML, but if that fails, we rely on the pretty HTML Contain(). - // to throw for improved error messages - if (!actualCompare.Contains(expectedCompare)) - actual.Should().Contain(expected); - } - - public static string CreateDiff(this string actual, string expected, bool sanitize = true) - { - expected = expected.Trim('\n').PrettyHtml(sanitize); - actual = actual.Trim('\n').PrettyHtml(sanitize); - var diffLines = InlineDiffBuilder.Diff(expected, actual).Lines; - - var mutatedCount = diffLines.Count( - l => - l.Type switch - { - ChangeType.Unchanged => false, - ChangeType.Deleted => true, - ChangeType.Inserted => true, - ChangeType.Imaginary => false, - ChangeType.Modified => true, - _ => false - } - ); - if (mutatedCount == 0) - return string.Empty; - - var actualLineLength = actual.Split("\n").Length; - if (mutatedCount >= actualLineLength) - { - return $$""" - Mutations {{mutatedCount}} on all {{actualLineLength}} showing - EXPECTED: - {{expected}} - ACTUAL: - {{actual}} - """; - } - - using var sw = new StringWriter(); - foreach (var l in diffLines) - { - switch (l.Type) - { - case ChangeType.Unchanged: - sw.WriteLine(l.Text); - break; - case ChangeType.Deleted: - sw.WriteLine("- " + l.Text); - break; - case ChangeType.Inserted: - sw.WriteLine("+ " + l.Text); - break; - case ChangeType.Imaginary: - sw.WriteLine("? " + l.Text); - break; - case ChangeType.Modified: - sw.WriteLine("+ " + l.Text); - break; - } - } - - return sw.ToString(); - } + ) => actual.Trim('\n').Should().ContainHtml(expected.Trim('\n'), Options(sanitize)); } From f99483ca2ea8fa642848da3471d36d1b6c2d1deb Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 12:39:54 +0100 Subject: [PATCH 03/18] Directives tests: snapshot the rendered admonition and dropdown HTML The dropdown title and applies_to tests each asserted a few Contain and NotContain fragments on the rendered HTML. They are now one whole-output snapshot per class (inline for the small dropdown outputs, a file for the nested-content dropdown and the applies_to admonitions, whose popover payload makes them long). The tests became stricter: every old fragment was checked to appear in (or, for NotContain, be absent from) the accepted snapshot before it was accepted. Co-Authored-By: Claude Sonnet 5.5 --- .../RendersHtml.verified.html | 13 ++ .../RendersHtml.verified.html | 34 ++++ .../RendersHtml.verified.html | 13 ++ .../RendersHtml.verified.html | 13 ++ .../RendersHtml.verified.html | 13 ++ .../RendersHtml.verified.html | 13 ++ .../Directives/AdmonitionTests.cs | 150 +++++++++--------- 7 files changed, 170 insertions(+), 79 deletions(-) create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/AdmonitionAppliesToTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/DropdownNestedContentTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ImportantAppliesToTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/NoteAppliesToTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/TipAppliesToTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/WarningAppliesToTests/RendersHtml.verified.html diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/AdmonitionAppliesToTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/AdmonitionAppliesToTests/RendersHtml.verified.html new file mode 100644 index 0000000000..c6be56cf00 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/AdmonitionAppliesToTests/RendersHtml.verified.html @@ -0,0 +1,13 @@ +
    +
    + Custom Admonition + + + + +
    +
    +

    This is a custom admonition with applies_to information

    +
    +
    +

    A regular paragraph.

    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/DropdownNestedContentTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/DropdownNestedContentTests/RendersHtml.verified.html new file mode 100644 index 0000000000..31c1672cea --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/DropdownNestedContentTests/RendersHtml.verified.html @@ -0,0 +1,34 @@ + +

    A regular paragraph.

    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ImportantAppliesToTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ImportantAppliesToTests/RendersHtml.verified.html new file mode 100644 index 0000000000..0b42684253 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ImportantAppliesToTests/RendersHtml.verified.html @@ -0,0 +1,13 @@ +
    +
    + Important + + + + +
    +
    +

    This is an important notice with applies_to information

    +
    +
    +

    A regular paragraph.

    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/NoteAppliesToTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/NoteAppliesToTests/RendersHtml.verified.html new file mode 100644 index 0000000000..c7ff61d071 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/NoteAppliesToTests/RendersHtml.verified.html @@ -0,0 +1,13 @@ +
    +
    + Note + + + + +
    +
    +

    This is a note with applies_to information

    +
    +
    +

    A regular paragraph.

    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/TipAppliesToTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/TipAppliesToTests/RendersHtml.verified.html new file mode 100644 index 0000000000..ce2e47ab2e --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/TipAppliesToTests/RendersHtml.verified.html @@ -0,0 +1,13 @@ +
    +
    + Tip + + + + +
    +
    +

    This is a tip with applies_to information

    +
    +
    +

    A regular paragraph.

    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/WarningAppliesToTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/WarningAppliesToTests/RendersHtml.verified.html new file mode 100644 index 0000000000..7e3616f91b --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/WarningAppliesToTests/RendersHtml.verified.html @@ -0,0 +1,13 @@ +
    +
    + Warning + + + + +
    +
    +

    This is a warning with applies_to information

    +
    +
    +

    A regular paragraph.

    diff --git a/tests/Elastic.Markdown.Tests/Directives/AdmonitionTests.cs b/tests/Elastic.Markdown.Tests/Directives/AdmonitionTests.cs index d2e17e5c0a..b3998aeea6 100644 --- a/tests/Elastic.Markdown.Tests/Directives/AdmonitionTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/AdmonitionTests.cs @@ -3,7 +3,9 @@ // See the LICENSE file in the project root for more information using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Admonition; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -120,11 +122,27 @@ Dropdown body content. public void StripsBackticksFromTitle() => Block!.Title.Should().Be("Deprecate elastic.apm settings"); [Test] - public void RendersPlainTextTitleInHtml() - { - Html.Should().Contain("Deprecate elastic.apm settings"); - Html.Should().NotContain("`elastic.apm`"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ + + """ + ); } [InheritsTests] @@ -140,11 +158,27 @@ Dropdown body content. public void StripsBoldMarkersFromTitle() => Block!.Title.Should().Be("Disable Save button"); [Test] - public void RendersBoldTitleAsPlainTextInHtml() - { - Html.Should().Contain("Disable Save button"); - Html.Should().NotContain("**Save**"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ + + """ + ); } [InheritsTests] @@ -160,11 +194,27 @@ Dropdown body content. public void StripsItalicMarkersFromTitle() => Block!.Title.Should().Be("Use italic emphasis"); [Test] - public void RendersItalicTitleAsPlainTextInHtml() - { - Html.Should().Contain("Use italic emphasis"); - Html.Should().NotContain("_italic_"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ + + """ + ); } [InheritsTests] @@ -250,35 +300,7 @@ A regular paragraph. public void SetsDropdownOpen() => Block!.DropdownOpen.Should().BeTrue(); [Test] - public void ContainsContentWithColons() - { - var html = Html; - html.Should().Contain("Time: 10:30 AM"); - // URL with port is NOT autolinked (excluded by autolink rules) - html.Should().Contain("URL: https://example.com:8080/path"); - html.Should().NotContain(""" Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -330,13 +352,7 @@ A regular paragraph. public void ParsesAppliesTo() => Block!.AppliesTo.Should().NotBeNull(); [Test] - public void RendersAppliesToInHtml() - { - var html = Html; - html.Should().Contain("applies applies-admonition"); - html.Should().Contain("admonition-title__separator"); - html.Should().Contain("applies-to-popover"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -363,13 +379,7 @@ A regular paragraph. public void ParsesAppliesTo() => Block!.AppliesTo.Should().NotBeNull(); [Test] - public void RendersAppliesToInHtml() - { - var html = Html; - html.Should().Contain("applies applies-admonition"); - html.Should().Contain("admonition-title__separator"); - html.Should().Contain("applies-to-popover"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -396,13 +406,7 @@ A regular paragraph. public void ParsesAppliesTo() => Block!.AppliesTo.Should().NotBeNull(); [Test] - public void RendersAppliesToInHtml() - { - var html = Html; - html.Should().Contain("applies applies-admonition"); - html.Should().Contain("admonition-title__separator"); - html.Should().Contain("applies-to-popover"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -429,13 +433,7 @@ A regular paragraph. public void ParsesAppliesTo() => Block!.AppliesTo.Should().NotBeNull(); [Test] - public void RendersAppliesToInHtml() - { - var html = Html; - html.Should().Contain("applies applies-admonition"); - html.Should().Contain("admonition-title__separator"); - html.Should().Contain("applies-to-popover"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -462,11 +460,5 @@ A regular paragraph. public void ParsesAppliesTo() => Block!.AppliesTo.Should().NotBeNull(); [Test] - public void RendersAppliesToInHtml() - { - var html = Html; - html.Should().Contain("applies applies-admonition"); - html.Should().Contain("admonition-title__separator"); - html.Should().Contain("applies-to-popover"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } From 241d43dbdb36d3466f02e950cac102228b4da6b7 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 12:40:04 +0100 Subject: [PATCH 04/18] Directives tests: snapshot the rendered agent skill HTML The Contain and NotContain fragment tests became whole-output snapshots per class, so the tests are stricter: every old fragment was checked to appear in (or, for NotContain, be absent from) the accepted snapshot before it was accepted. Small outputs are inline snapshots, the rest are files. Co-Authored-By: Claude Sonnet 5.5 --- .../RendersHtml.verified.html | 23 ++++++++++++++ .../AgentSkillTests/RendersHtml.verified.html | 23 ++++++++++++++ .../Directives/AgentSkillTests.cs | 31 +++---------------- 3 files changed, 50 insertions(+), 27 deletions(-) create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/AgentSkillNoSkillNameTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/AgentSkillTests/RendersHtml.verified.html diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/AgentSkillNoSkillNameTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/AgentSkillNoSkillNameTests/RendersHtml.verified.html new file mode 100644 index 0000000000..bdb7a52031 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/AgentSkillNoSkillNameTests/RendersHtml.verified.html @@ -0,0 +1,23 @@ + +

    A regular paragraph.

    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/AgentSkillTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/AgentSkillTests/RendersHtml.verified.html new file mode 100644 index 0000000000..ec0c0fe6af --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/AgentSkillTests/RendersHtml.verified.html @@ -0,0 +1,23 @@ +
    +
    + + + + Agent skill available +
    +
    +
    +

    A skill is available to help AI agents with this topic.

    +

    + Learn more about agent skills for Elastic +

    +
    + +
    +
    +

    A regular paragraph.

    diff --git a/tests/Elastic.Markdown.Tests/Directives/AgentSkillTests.cs b/tests/Elastic.Markdown.Tests/Directives/AgentSkillTests.cs index 12b0c7d2a6..e8c961a86f 100644 --- a/tests/Elastic.Markdown.Tests/Directives/AgentSkillTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/AgentSkillTests.cs @@ -3,7 +3,9 @@ // See the LICENSE file in the project root for more information using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.AgentSkill; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -33,12 +35,7 @@ A regular paragraph. public void SetsDirective() => Block!.Directive.Should().Be("agent-skill"); [Test] - public void RendersAgentSkillDiv() - { - Html.Should().Contain("class=\"agent-skill\""); - Html.Should().Contain("class=\"agent-skill-header\""); - Html.Should().Contain("class=\"agent-skill-content\""); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); [Test] public void RendersTitle() => Html.Should().Contain("Agent skill available"); @@ -46,21 +43,6 @@ public void RendersAgentSkillDiv() [Test] public void RendersDefaultText() => Html.Should().Contain("A skill is available to help AI agents with this topic."); - [Test] - public void RendersLearnMoreLink() - { - Html.Should().Contain("Learn more about agent skills for Elastic"); - Html.Should().Contain("href=\"https://www.elastic.co/docs/explore-analyze/ai-features/agent-skills#available-skills\""); - } - - [Test] - public void RendersCopyButton() - { - Html.Should().Contain("class=\"agent-skill-button\""); - Html.Should().Contain("Copy install command"); - Html.Should().Contain("data-copy-text=\"npx skills add elastic/agent-skills@elasticsearch-esql\""); - } - [Test] public void DoesNotRenderLinkButton() => Html.Should().NotContain("Get the skill"); } @@ -132,10 +114,5 @@ A regular paragraph. public void InstallCommandIsNull() => Block!.InstallCommand.Should().BeNull(); [Test] - public void FallsBackToLinkButton() - { - Html.Should().Contain("Get the skill"); - Html.Should().Contain("href=\"https://github.com/elastic/agent-skills\""); - Html.Should().NotContain("data-copy-text"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } From b62141606aa11fc5ecc395c2cebac70b6258d022 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 12:40:07 +0100 Subject: [PATCH 05/18] Directives tests: snapshot the rendered changelog HTML The Contain and NotContain fragment tests became whole-output snapshots per class, so the tests are stricter: every old fragment was checked to appear in (or, for NotContain, be absent from) the accepted snapshot before it was accepted. Small outputs are inline snapshots, the rest are files. The changelog tests that only asserted NotContain on an otherwise empty output (empty bundles, filtered-out types) now assert the empty output and an empty diagnostics list, which a failing directive no longer satisfies. Co-Authored-By: Claude Sonnet 5.5 --- .../RendersHtml.verified.html | 25 ++ .../RendersHtml.verified.html | 25 ++ .../RendersHtml.verified.html | 25 ++ .../RendersHtml.verified.html | 48 ++++ .../RendersHtml.verified.html | 25 ++ .../RendersHtml.verified.html | 25 ++ .../RendersHtml.verified.html | 46 ++++ .../RendersHtml.verified.html | 43 +++ .../RendersHtml.verified.html | 33 +++ .../RendersHtml.verified.html | 45 ++++ .../RendersHtml.verified.html | 37 +++ .../RendersHtml.verified.html | 29 ++ .../RendersHtml.verified.html | 28 ++ .../RendersHtml.verified.html | 27 ++ .../RendersHtml.verified.html | 69 +++++ .../RendersHtml.verified.html | 28 ++ .../RendersHtml.verified.html | 69 +++++ .../RendersHtml.verified.html | 25 ++ .../RendersHtml.verified.html | 28 ++ .../RendersHtml.verified.html | 28 ++ .../RendersHtml.verified.html | 38 +++ .../RendersHtml.verified.html | 56 ++++ .../RendersHtml.verified.html | 28 ++ .../RendersHtml.verified.html | 45 ++++ .../RendersHtml.verified.html | 30 +++ .../RendersHtml.verified.html | 46 ++++ .../RendersHtml.verified.html | 25 ++ .../RendersHtml.verified.html | 60 +++++ .../RendersHtml.verified.html | 45 ++++ .../RendersHtml.verified.html | 21 ++ .../RendersHtml.verified.html | 21 ++ .../RendersHtml.verified.html | 35 +++ .../RendersHtml.verified.html | 30 +++ .../RendersHtml.verified.html | 26 ++ .../RendersHtml.verified.html | 79 ++++++ .../RendersHtml.verified.html | 33 +++ .../RendersHtml.verified.html | 25 ++ .../RendersHtml.verified.html | 29 ++ .../RendersHtml.verified.html | 29 ++ .../RendersHtml.verified.html | 44 ++++ .../RendersHtml.verified.html | 23 ++ .../RendersHtml.verified.html | 23 ++ .../RendersHtml.verified.html | 55 ++++ .../Directives/ChangelogBasicTests.cs | 156 ++++++----- .../Directives/ChangelogConfigTests.cs | 87 +++--- .../Directives/ChangelogDropdownsTests.cs | 136 +++------- .../ChangelogFlattenedLinksTests.cs | 27 +- .../Directives/ChangelogHideLinksTests.cs | 39 ++- .../ChangelogHighlightsOptionTests.cs | 75 +++--- .../Directives/ChangelogMergeTests.cs | 29 +- .../ChangelogPathResolutionTests.cs | 46 ++-- .../Directives/ChangelogSubsectionsTests.cs | 85 +++--- .../Directives/ChangelogTocFilteringTests.cs | 73 ++++-- .../Directives/ChangelogTypeFilterTests.cs | 248 ++++++++---------- .../ChangelogVersionSortingTests.cs | 77 +----- 55 files changed, 2031 insertions(+), 601 deletions(-) create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogBasicTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogCombinedFiltersFilterTocTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigAndBundlesRelativePathsTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigLoadAutoDiscoverTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigLoadExplicitPathTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigLoadFromDocsSubfolderTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigMixedBlockersTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigPriorityTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigRelativePathTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDateVersionedBundlesTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDedicatedPageWithSubsectionsTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsDefaultTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsEnabledTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsEnabledWithHiddenDescriptionsTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsExplicitWithDifferentTypesTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsPlainTextTitleTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsWithDifferentTypesTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHeaderLevelsTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsLegacyTypeHighlightTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionDefaultOffTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionEnabledTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionWithTypeAllTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogKeepHighlightDescriptionsTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMergeDifferentTargetsTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMergeMixedVersionTypesTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMergeSameTargetTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMergeSingleBundleTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMixedVersionTypesTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMultipleBundlesTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogProductExplicitOptionOverridesDocsetTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogProductFallbackSingleProductTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogPublishBlockerFiltersTocTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogRawVersionFallbackTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogSubsectionsEnabledTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterAllTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterCaseInsensitiveTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterDefaultTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterDeprecationTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterKnownIssueTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterWithSubsectionsTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogWithBreakingChangesTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogWithDeprecationsTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogYearMonthVersionTests/RendersHtml.verified.html diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogBasicTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogBasicTests/RendersHtml.verified.html new file mode 100644 index 0000000000..2c4dbafab2 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogBasicTests/RendersHtml.verified.html @@ -0,0 +1,25 @@ +
    +

    + 9.3.0 +

    +
    + + +
    +

    + Fixes +

    +
    +
      +
    • Fix important bug. + #123457 +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogCombinedFiltersFilterTocTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogCombinedFiltersFilterTocTests/RendersHtml.verified.html new file mode 100644 index 0000000000..a8b453c084 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogCombinedFiltersFilterTocTests/RendersHtml.verified.html @@ -0,0 +1,25 @@ +
    +

    + 9.3.0 +

    +
    + + +
    +

    + Documentation +

    +
    +
      +
    • Docs entry blocked by type. + #222222 +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigAndBundlesRelativePathsTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigAndBundlesRelativePathsTests/RendersHtml.verified.html new file mode 100644 index 0000000000..decd978f97 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigAndBundlesRelativePathsTests/RendersHtml.verified.html @@ -0,0 +1,25 @@ +
    +

    + 1.0.0 +

    +
    + +
      +
    • Combined feature. + #66666 +
    • +
    +
    +

    + Other changes +

    +
    +
      +
    • Blocked by config. + #77777 +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigLoadAutoDiscoverTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigLoadAutoDiscoverTests/RendersHtml.verified.html new file mode 100644 index 0000000000..48387d6986 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigLoadAutoDiscoverTests/RendersHtml.verified.html @@ -0,0 +1,48 @@ +
    +

    + 9.3.0 +

    +
    +
    +

    + Known issues +

    +
    +
      +
    • +

      Known issue. + #333333 +

      +

      + Impact: + Some users may be affected.

      +
    • +
    +
    +

    + Deprecations +

    +
    +
      +
    • +

      Deprecation notice. + #222222 +

      +

      + Impact: + Users should migrate.

      +

      + Action: + Use the new API.

      +
    • +
    + + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigLoadExplicitPathTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigLoadExplicitPathTests/RendersHtml.verified.html new file mode 100644 index 0000000000..c0e028afaf --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigLoadExplicitPathTests/RendersHtml.verified.html @@ -0,0 +1,25 @@ +
    +

    + 9.3.0 +

    +
    + + +
    +

    + Documentation +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigLoadFromDocsSubfolderTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigLoadFromDocsSubfolderTests/RendersHtml.verified.html new file mode 100644 index 0000000000..c166523232 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigLoadFromDocsSubfolderTests/RendersHtml.verified.html @@ -0,0 +1,25 @@ +
    +

    + 9.3.0 +

    +
    + + +
    +

    + Other changes +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigMixedBlockersTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigMixedBlockersTests/RendersHtml.verified.html new file mode 100644 index 0000000000..74d64b0c4b --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigMixedBlockersTests/RendersHtml.verified.html @@ -0,0 +1,46 @@ +
    +

    + 9.3.0 +

    +
    +
    +

    + Deprecations +

    +
    +
      +
    • +

      Deprecation in Search. + #222222 +

      +

      + Impact: + None.

      +

      + Action: + Upgrade.

      +
    • +
    + +
      +
    • Regular feature in Search. + #111111 +
    • +
    • Feature in Internal. + #333333 +
    • +
    +
    +

    + Fixes +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigPriorityTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigPriorityTests/RendersHtml.verified.html new file mode 100644 index 0000000000..cc61daced0 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigPriorityTests/RendersHtml.verified.html @@ -0,0 +1,43 @@ +
    +

    + 9.3.0 +

    +
    +
    +

    + Deprecations +

    +
    +
      +
    • +

      Deprecation notice. + #222222 +

      +

      + Impact: + None.

      +

      + Action: + Upgrade.

      +
    • +
    + + +
    +

    + Other changes +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigRelativePathTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigRelativePathTests/RendersHtml.verified.html new file mode 100644 index 0000000000..aa149a419a --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogConfigRelativePathTests/RendersHtml.verified.html @@ -0,0 +1,33 @@ +
    +

    + 1.0.0 +

    +
    +
    +

    + Deprecations +

    +
    +
      +
    • +

      Blocked entry. + #22222 +

      +

      + Impact: + None.

      +

      + Action: + Upgrade.

      +
    • +
    + +
      +
    • Feature entry. + #11111 +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDateVersionedBundlesTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDateVersionedBundlesTests/RendersHtml.verified.html new file mode 100644 index 0000000000..0541309213 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDateVersionedBundlesTests/RendersHtml.verified.html @@ -0,0 +1,45 @@ + + +
      +
    • August 15th feature. + #222222 +
    • +
    + + +
      +
    • August 5th feature. + #333333 +
    • +
    + + +
      +
    • August 1st feature. + #111111 +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDedicatedPageWithSubsectionsTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDedicatedPageWithSubsectionsTests/RendersHtml.verified.html new file mode 100644 index 0000000000..e9a78c39cc --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDedicatedPageWithSubsectionsTests/RendersHtml.verified.html @@ -0,0 +1,37 @@ +
    +

    + 9.3.0 +

    +
    +

    + Indexing +

    +
      +
    • +

      Indexing deprecation. + #666666 +

      +

      + Impact: + Will be removed.

      +

      + Action: + Update config.

      +
    • +
    +

    + Search +

    +
      +
    • +

      Search deprecation. + #555555 +

      +

      + Impact: + Will be removed.

      +

      + Action: + Migrate to new API.

      +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsDefaultTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsDefaultTests/RendersHtml.verified.html new file mode 100644 index 0000000000..841d1b8b15 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsDefaultTests/RendersHtml.verified.html @@ -0,0 +1,29 @@ +
    +

    + 9.3.0 +

    +
    +
      +
    • +

      Breaking API change. + #333333 + API has been changed to improve performance.

      +

      + Impact: + Existing API calls will fail.

      +

      + Action: + Update your code to use the new API endpoints.

      +
    • +
    • +

      Another breaking change. + #444444 + Removed deprecated parameter.

      +

      + Impact: + Scripts using the old parameter will fail.

      +

      + Action: + Remove references to the deprecated parameter.

      +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsEnabledTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsEnabledTests/RendersHtml.verified.html new file mode 100644 index 0000000000..7767361084 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsEnabledTests/RendersHtml.verified.html @@ -0,0 +1,28 @@ +
    +

    + 9.3.0 +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsEnabledWithHiddenDescriptionsTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsEnabledWithHiddenDescriptionsTests/RendersHtml.verified.html new file mode 100644 index 0000000000..5252390011 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsEnabledWithHiddenDescriptionsTests/RendersHtml.verified.html @@ -0,0 +1,27 @@ +
    +

    + 9.3.0 +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsExplicitWithDifferentTypesTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsExplicitWithDifferentTypesTests/RendersHtml.verified.html new file mode 100644 index 0000000000..9c29887a47 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsExplicitWithDifferentTypesTests/RendersHtml.verified.html @@ -0,0 +1,69 @@ +
    +

    + 9.3.0 +

    +
    + + +
    +

    + Known issues +

    +
    + + + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsPlainTextTitleTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsPlainTextTitleTests/RendersHtml.verified.html new file mode 100644 index 0000000000..4969e776d3 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsPlainTextTitleTests/RendersHtml.verified.html @@ -0,0 +1,28 @@ +
    +

    + 9.3.0 +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsWithDifferentTypesTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsWithDifferentTypesTests/RendersHtml.verified.html new file mode 100644 index 0000000000..f44377a563 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogDropdownsWithDifferentTypesTests/RendersHtml.verified.html @@ -0,0 +1,69 @@ +
    +

    + 9.3.0 +

    +
    + +
      +
    • +

      Breaking API change. + #222222 +

      +

      + Impact: + Users must update.

      +

      + Action: + Follow guide.

      +
    • +
    +
    +

    + Known issues +

    +
    +
      +
    • +

      Known issue with search. + #333333 +

      +

      + Impact: + Search results incomplete.

      +

      + Action: + Use workaround.

      +
    • +
    +
    +

    + Deprecations +

    +
    +
      +
    • +

      Deprecated API. + #444444 +

      +

      + Impact: + Will be removed in future.

      +

      + Action: + Use new API.

      +
    • +
    + + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHeaderLevelsTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHeaderLevelsTests/RendersHtml.verified.html new file mode 100644 index 0000000000..7810fca245 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHeaderLevelsTests/RendersHtml.verified.html @@ -0,0 +1,25 @@ +
    +

    + 9.3.0 +

    +
    + + +
    +

    + Fixes +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsLegacyTypeHighlightTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsLegacyTypeHighlightTests/RendersHtml.verified.html new file mode 100644 index 0000000000..de65823ef9 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsLegacyTypeHighlightTests/RendersHtml.verified.html @@ -0,0 +1,28 @@ +
    +

    + 9.3.0 +

    +
    + + +
    +

    + Fixes +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionDefaultOffTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionDefaultOffTests/RendersHtml.verified.html new file mode 100644 index 0000000000..de65823ef9 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionDefaultOffTests/RendersHtml.verified.html @@ -0,0 +1,28 @@ +
    +

    + 9.3.0 +

    +
    + + +
    +

    + Fixes +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionEnabledTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionEnabledTests/RendersHtml.verified.html new file mode 100644 index 0000000000..79289fea48 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionEnabledTests/RendersHtml.verified.html @@ -0,0 +1,38 @@ +
    +

    + 9.3.0 +

    +
    +
    +

    + Highlights +

    +
    +
      +
    • Highlighted feature. + #111111 +
    • +
    + + +
    +

    + Fixes +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionWithTypeAllTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionWithTypeAllTests/RendersHtml.verified.html new file mode 100644 index 0000000000..6dd7cf3495 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionWithTypeAllTests/RendersHtml.verified.html @@ -0,0 +1,56 @@ +
    +

    + 9.3.0 +

    +
    + +
      +
    • +

      Breaking API change. + #444444 +

      +

      + Impact: + Users must update.

      +

      + Action: + Follow guide.

      +
    • +
    +
    +

    + Highlights +

    +
    +
      +
    • Highlighted feature. + #111111 +
    • +
    + + +
    +

    + Fixes +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogKeepHighlightDescriptionsTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogKeepHighlightDescriptionsTests/RendersHtml.verified.html new file mode 100644 index 0000000000..2f51d2b72e --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogKeepHighlightDescriptionsTests/RendersHtml.verified.html @@ -0,0 +1,28 @@ +
    +

    + 9.3.0 +

    +
    +
    +

    + Highlights +

    +
    +
      +
    • Highlighted feature. + #111111 + This is the highlight description.
    • +
    + + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMergeDifferentTargetsTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMergeDifferentTargetsTests/RendersHtml.verified.html new file mode 100644 index 0000000000..a120562468 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMergeDifferentTargetsTests/RendersHtml.verified.html @@ -0,0 +1,45 @@ +
    +

    + 9.3.0 +

    +
    + + +
    +

    + 9.2.0 +

    +
    + + +
    +

    + 9.1.0 +

    +
    + + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMergeMixedVersionTypesTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMergeMixedVersionTypesTests/RendersHtml.verified.html new file mode 100644 index 0000000000..5fdb52403d --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMergeMixedVersionTypesTests/RendersHtml.verified.html @@ -0,0 +1,30 @@ +
    +

    + 9.3.0 +

    +
    + + + + +
      +
    • Feature for August 5th. + #222222 +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMergeSameTargetTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMergeSameTargetTests/RendersHtml.verified.html new file mode 100644 index 0000000000..6bcf77e793 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMergeSameTargetTests/RendersHtml.verified.html @@ -0,0 +1,46 @@ + + +
      +
    • Kibana feature for August 5th. + #111111 +
    • +
    • Elasticsearch feature for August 5th. + #222222 +
    • +
    • Serverless feature for August 5th. + #333333 +
    • +
    +
    +

    + Fixes +

    +
    +
      +
    • Elasticsearch bugfix for August 5th. + #222223 +
    • +
    + + +
      +
    • Kibana feature for August 1st. + #444444 +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMergeSingleBundleTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMergeSingleBundleTests/RendersHtml.verified.html new file mode 100644 index 0000000000..78fcba1ade --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMergeSingleBundleTests/RendersHtml.verified.html @@ -0,0 +1,25 @@ +
    +

    + 9.3.0 +

    +
    + + +
    +

    + Fixes +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMixedVersionTypesTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMixedVersionTypesTests/RendersHtml.verified.html new file mode 100644 index 0000000000..6a92f4994a --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMixedVersionTypesTests/RendersHtml.verified.html @@ -0,0 +1,60 @@ +
    +

    + 9.3.0 +

    +
    + +
      +
    • Semver 9.3.0 feature. + #111111 +
    • +
    +
    +

    + 9.2.0 +

    +
    + +
      +
    • Semver 9.2.0 feature. + #222222 +
    • +
    + + +
      +
    • Date-based feature. + #333333 +
    • +
    +
    +

    + July 1, 2025 +

    +
    + +
      +
    • Earlier date feature. + #444444 +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMultipleBundlesTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMultipleBundlesTests/RendersHtml.verified.html new file mode 100644 index 0000000000..b475506cca --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogMultipleBundlesTests/RendersHtml.verified.html @@ -0,0 +1,45 @@ +
    +

    + 9.10.0 +

    +
    + +
      +
    • Feature in 9.10.0. + #333333 +
    • +
    +
    +

    + 9.3.0 +

    +
    + + +
    +

    + 9.2.0 +

    +
    + + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogProductExplicitOptionOverridesDocsetTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogProductExplicitOptionOverridesDocsetTests/RendersHtml.verified.html new file mode 100644 index 0000000000..e0a70ea13f --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogProductExplicitOptionOverridesDocsetTests/RendersHtml.verified.html @@ -0,0 +1,21 @@ +
    +

    + 9.3.0 +

    +
    + +
      +
    • Regular feature. + #111111 +
    • +
    • ES Internal feature. + #222222 +
    • +
    • Kibana Internal feature. + #333333 +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogProductFallbackSingleProductTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogProductFallbackSingleProductTests/RendersHtml.verified.html new file mode 100644 index 0000000000..d66e89d74f --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogProductFallbackSingleProductTests/RendersHtml.verified.html @@ -0,0 +1,21 @@ +
    +

    + 9.3.0 +

    +
    + +
      +
    • Regular Kibana feature. + #111111 +
    • +
    • Internal feature. + #222222 +
    • +
    • Observability feature. + #333333 +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogPublishBlockerFiltersTocTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogPublishBlockerFiltersTocTests/RendersHtml.verified.html new file mode 100644 index 0000000000..23c242f583 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogPublishBlockerFiltersTocTests/RendersHtml.verified.html @@ -0,0 +1,35 @@ +
    +

    + 9.3.0 +

    +
    + + +
    +

    + Documentation +

    +
    + +
    +

    + Other changes +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogRawVersionFallbackTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogRawVersionFallbackTests/RendersHtml.verified.html new file mode 100644 index 0000000000..a5316b2553 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogRawVersionFallbackTests/RendersHtml.verified.html @@ -0,0 +1,30 @@ +
    +

    + release-beta +

    +
    + +
      +
    • Beta release feature. + #222222 +
    • +
    +
    +

    + release-alpha +

    +
    + +
      +
    • Alpha release feature. + #111111 +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogSubsectionsEnabledTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogSubsectionsEnabledTests/RendersHtml.verified.html new file mode 100644 index 0000000000..137366d96b --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogSubsectionsEnabledTests/RendersHtml.verified.html @@ -0,0 +1,26 @@ +
    +

    + 9.3.0 +

    +
    + +

    + Indexing +

    +
      +
    • Feature in Indexing. + #222222 +
    • +
    +

    + Search +

    +
      +
    • Feature in Search. + #111111 +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterAllTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterAllTests/RendersHtml.verified.html new file mode 100644 index 0000000000..777aa8ea61 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterAllTests/RendersHtml.verified.html @@ -0,0 +1,79 @@ +
    +

    + 9.3.0 +

    +
    + +
      +
    • +

      Breaking API change. + #333333 +

      +

      + Impact: + Users must update.

      +

      + Action: + Follow guide.

      +
    • +
    +
    +

    + Known issues +

    +
    +
      +
    • +

      Known issue. + #444444 +

      +

      + Impact: + Some impact.

      +

      + Action: + Workaround available.

      +
    • +
    +
    +

    + Deprecations +

    +
    +
      +
    • +

      Deprecated feature. + #555555 +

      +

      + Impact: + Will be removed.

      +

      + Action: + Use new feature.

      +
    • +
    + + +
    +

    + Fixes +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterCaseInsensitiveTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterCaseInsensitiveTests/RendersHtml.verified.html new file mode 100644 index 0000000000..d3ae5b2f2d --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterCaseInsensitiveTests/RendersHtml.verified.html @@ -0,0 +1,33 @@ +
    +

    + 9.3.0 +

    +
    + +
      +
    • +

      Breaking change. + #222222 +

      +

      + Impact: + Impact.

      +

      + Action: + Action.

      +
    • +
    + + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterDefaultTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterDefaultTests/RendersHtml.verified.html new file mode 100644 index 0000000000..7810fca245 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterDefaultTests/RendersHtml.verified.html @@ -0,0 +1,25 @@ +
    +

    + 9.3.0 +

    +
    + + +
    +

    + Fixes +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterDeprecationTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterDeprecationTests/RendersHtml.verified.html new file mode 100644 index 0000000000..029e3f94af --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterDeprecationTests/RendersHtml.verified.html @@ -0,0 +1,29 @@ +
    +

    + 9.3.0 +

    +
    +
      +
    • +

      Deprecated feature. + #555555 +

      +

      + Impact: + Will be removed.

      +

      + Action: + Use new feature.

      +
    • +
    • +

      Another deprecation. + #666666 +

      +

      + Impact: + Also will be removed.

      +

      + Action: + Migrate to new API.

      +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterKnownIssueTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterKnownIssueTests/RendersHtml.verified.html new file mode 100644 index 0000000000..3904bff7b9 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterKnownIssueTests/RendersHtml.verified.html @@ -0,0 +1,29 @@ +
    +

    + 9.3.0 +

    +
    +
      +
    • +

      Known issue 1. + #444444 +

      +

      + Impact: + Some impact.

      +

      + Action: + Workaround available.

      +
    • +
    • +

      Known issue 2. + #555555 +

      +

      + Impact: + Different impact.

      +

      + Action: + Different workaround.

      +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterWithSubsectionsTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterWithSubsectionsTests/RendersHtml.verified.html new file mode 100644 index 0000000000..aa4709810f --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogTypeFilterWithSubsectionsTests/RendersHtml.verified.html @@ -0,0 +1,44 @@ +
    +

    + 9.3.0 +

    +
    + +
      +
    • +

      Breaking change. + #333333 +

      +

      + Impact: + Impact.

      +

      + Action: + Action.

      +
    • +
    + +

    + Indexing +

    + +

    + Search +

    + diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogWithBreakingChangesTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogWithBreakingChangesTests/RendersHtml.verified.html new file mode 100644 index 0000000000..6b9b96e5fd --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogWithBreakingChangesTests/RendersHtml.verified.html @@ -0,0 +1,23 @@ +
    +

    + 9.3.0 +

    +
    + +
      +
    • +

      Breaking change in API. + #222222 +

      +

      + Impact: + Users must update their code.

      +

      + Action: + Follow the migration guide.

      +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogWithDeprecationsTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogWithDeprecationsTests/RendersHtml.verified.html new file mode 100644 index 0000000000..424f70e222 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogWithDeprecationsTests/RendersHtml.verified.html @@ -0,0 +1,23 @@ +
    +

    + 9.3.0 +

    +
    +
    +

    + Deprecations +

    +
    +
      +
    • +

      Deprecated old API. + #333333 +

      +

      + Impact: + The API will be removed in a future version.

      +

      + Action: + Use the new API instead.

      +
    • +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogYearMonthVersionTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogYearMonthVersionTests/RendersHtml.verified.html new file mode 100644 index 0000000000..ce0ca09567 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogYearMonthVersionTests/RendersHtml.verified.html @@ -0,0 +1,55 @@ +
    +

    + December 2025 +

    +
    + + +
    +

    + October 2025 +

    +
    + + +
    +

    + August 2025 +

    +
    + + +
    +

    + Fixes +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs index 5f870e10c2..2301ba55e6 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs @@ -9,7 +9,10 @@ using Elastic.Documentation.Configuration; using Elastic.Documentation.Configuration.ReleaseNotes; using Elastic.Documentation.ReleaseNotes; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -74,14 +77,7 @@ public void SetsCorrectBundlesFolderPath() => public void LoadsBundles() => Block!.LoadedBundles.Should().HaveCount(1); [Test] - public void RendersMarkdownContent() - { - Html.Should().Contain("9.3.0"); - Html.Should().Contain("Features and enhancements"); - Html.Should().Contain("Add new feature"); - Html.Should().Contain("Fixes"); - Html.Should().Contain("Fix important bug"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -143,11 +139,27 @@ public ChangelogExcludeAmendTests() : base( } [Test] - public void RendersWithoutExcludedEntry() - { - Html.Should().Contain("Keep this feature"); - Html.Should().NotContain("Remove this feature"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + +
      +
    • Keep this feature. + #123456 +
    • +
    + """ + ); [Test] public void LoadsMergedEntryCount() @@ -247,12 +259,7 @@ public void RendersInSemverOrder() } [Test] - public void RendersAllVersions() - { - Html.Should().Contain("9.10.0"); - Html.Should().Contain("9.3.0"); - Html.Should().Contain("9.2.0"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// @@ -319,11 +326,27 @@ public ChangelogVersionFilterTests() : base( public void LoadsOnlyMatchingBundle() => Block!.LoadedBundles.Should().ContainSingle().Which.Version.Should().Be("9.3.0"); [Test] - public void RendersOnlyMatchingVersion() - { - Html.Should().Contain("Feature in 9.3.0"); - Html.Should().NotContain("Feature in 9.2.0"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + +
      +
    • Feature in 9.3.0. + #222222 +
    • +
    + """ + ); } /// @@ -407,11 +430,27 @@ public void SetsCorrectBundlesFolderPath() => Block!.BundlesFolderPath.Should().EndWith("release-notes/bundles".Replace('/', Path.DirectorySeparatorChar)); [Test] - public void RendersContent() - { - Html.Should().Contain("1.0.0"); - Html.Should().Contain("First release"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 1.0.0 +

    +
    + +
      +
    • First release. + #1 +
    • +
    + """ + ); } /// @@ -487,12 +526,27 @@ protected override IReleaseNotesResolver GetReleaseNotesResolver() => public void FoundFromCdn() => Block!.Found.Should().BeTrue(); [Test] - public void RendersCdnBundleBody() - { - Html.Should().Contain("9.4.0"); - Html.Should().Contain("Features and enhancements"); - Html.Should().Contain("Faster vector search on the CDN"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.4.0 +

    +
    + +
      +
    • Faster vector search on the CDN. + #999 +
    • +
    + """ + ); } /// @@ -765,20 +819,7 @@ public ChangelogWithBreakingChangesTests() : base( ); [Test] - public void RendersBreakingChangesSection() - { - Html.Should().Contain("Breaking changes"); - Html.Should().Contain("Breaking change in API"); - } - - [Test] - public void RendersImpactAndAction() - { - Html.Should().Contain("Impact"); - Html.Should().Contain("Users must update their code"); - Html.Should().Contain("Action"); - Html.Should().Contain("Follow the migration guide"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// @@ -820,11 +861,7 @@ public ChangelogWithDeprecationsTests() : base( ); [Test] - public void RendersDeprecationsSection() - { - Html.Should().Contain("Deprecations"); - Html.Should().Contain("Deprecated old API"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -853,8 +890,8 @@ public ChangelogEmptyBundleTests() : base( [Test] public void OmitsEmptyVersionBlock() { - Html.Should().NotContain("No new features, enhancements, or fixes"); - Html.Should().NotContain("9.3.0"); + Html.Should().MatchInlineHtmlSnapshot(html: DocsHtml.Body(), expected: ""); + Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } } @@ -1078,12 +1115,7 @@ public ChangelogHeaderLevelsTests() : base( ); [Test] - public void VersionHeaderIsH2() - { - // Version should be h2 - Html.Should().Contain(" Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); [Test] public void OnlyOneH2ForVersion() diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogConfigTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogConfigTests.cs index 16ed475190..42ad181db4 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogConfigTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogConfigTests.cs @@ -4,7 +4,9 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -80,13 +82,7 @@ public ChangelogConfigLoadAutoDiscoverTests() : base( public void PublishBlockerIsNull() => Block!.PublishBlocker.Should().BeNull(); [Test] - public void RendersAllEntries_NoFiltering() - { - // Directive does not apply rules.publish; all entries are shown - Html.Should().Contain("Regular feature"); - Html.Should().Contain("Deprecation notice"); - Html.Should().Contain("Known issue"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); [Test] public void RendersFeaturesSection() => Html.Should().Contain("Features and enhancements"); @@ -161,12 +157,7 @@ public ChangelogConfigLoadExplicitPathTests() : base( public void PublishBlockerIsNull() => Block!.PublishBlocker.Should().BeNull(); [Test] - public void RendersAllEntries_NoFiltering() - { - // Directive does not apply rules.publish; all entries are shown - Html.Should().Contain("Regular feature"); - Html.Should().Contain("Internal docs"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -226,12 +217,7 @@ public ChangelogConfigLoadFromDocsSubfolderTests() : base( public void PublishBlockerIsNull() => Block!.PublishBlocker.Should().BeNull(); [Test] - public void RendersAllEntries_NoFiltering() - { - // Directive does not apply rules.publish; all entries are shown - Html.Should().Contain("Regular feature"); - Html.Should().Contain("Other change"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -394,13 +380,7 @@ public ChangelogConfigPriorityTests() : base( } [Test] - public void RendersAllEntries_NoPublishFiltering() - { - // Directive does not apply rules.publish; all entries are shown - Html.Should().Contain("Regular feature"); - Html.Should().Contain("Deprecation notice"); - Html.Should().Contain("Other change"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -540,14 +520,7 @@ public ChangelogConfigMixedBlockersTests() : base( public void PublishBlockerIsNull() => Block!.PublishBlocker.Should().BeNull(); [Test] - public void RendersAllEntries_NoFiltering() - { - // Directive does not apply rules.publish; all entries are shown - Html.Should().Contain("Regular feature in Search"); - Html.Should().Contain("Deprecation in Search"); - Html.Should().Contain("Feature in Internal"); - Html.Should().Contain("Bug fix"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -625,13 +598,7 @@ protected override void AddToFileSystem(MockFileSystem fileSystem) public void PublishBlockerIsNull() => Block!.PublishBlocker.Should().BeNull(); [Test] - public void RendersAllEntries_NoFiltering() - { - // Directive does not apply rules.publish; all entries are shown - Html.Should().Contain("Regular Kibana feature"); - Html.Should().Contain("Internal feature"); - Html.Should().Contain("Observability feature"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -700,12 +667,30 @@ protected override void AddToFileSystem(MockFileSystem fileSystem) public void PublishBlockerIsNull() => Block!.PublishBlocker.Should().BeNull(); [Test] - public void RendersAllEntries_NoFiltering() - { - // Directive does not apply rules.publish; all entries are shown - Html.Should().Contain("Regular feature"); - Html.Should().Contain("Internal feature"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + + + """ + ); } [InheritsTests] @@ -792,11 +777,5 @@ public void EmitsDeprecationWarningForProductOption() => Collector.Diagnostics.Should().Contain(d => d.Message.Contains(":product:") && d.Message.Contains("deprecated")); [Test] - public void RendersAllEntries_NoFiltering() - { - // Directive does not apply rules.publish; all entries are shown - Html.Should().Contain("Regular feature"); - Html.Should().Contain("ES Internal feature"); - Html.Should().Contain("Kibana Internal feature"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogDropdownsTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogDropdownsTests.cs index 9a5d80679b..acb2cc35ea 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogDropdownsTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogDropdownsTests.cs @@ -4,7 +4,9 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -63,34 +65,7 @@ public ChangelogDropdownsDefaultTests() : base( public void DefaultBehaviorDoesNotParseDropdownsOption() => Block!.DropdownsEnabled.Should().BeFalse(); [Test] - public void DefaultBehaviorRendersFlattened() - { - // Should NOT contain dropdown HTML structure - Html.Should().NotContain("
    "); - Html.Should().NotContain("dropdown-title__summary-text"); - - // Should contain bulleted list format without bold titles (matching regular entries) - Html.Should().Contain("Breaking API change."); - Html.Should().Contain("Another breaking change."); - } - - [Test] - public void DefaultBehaviorIncludesImpactAndActionSections() - { - Html.Should().Contain("Impact: Existing API calls will fail."); - Html.Should().Contain("Action: Update your code to use the new API endpoints."); - Html.Should().Contain("Impact: Scripts using the old parameter will fail."); - Html.Should().Contain("Action: Remove references to the deprecated parameter."); - } - - [Test] - public void DefaultBehaviorIncludesDescriptions() - { - // Note: Descriptions may be hidden by default due to :description-visibility: auto behavior - // This test validates that when descriptions are shown, they appear in the correct format - Html.Should().Contain("API has been changed to improve performance."); - Html.Should().Contain("Removed deprecated parameter."); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// @@ -136,26 +111,10 @@ public ChangelogDropdownsEnabledTests() : base( public void ExplicitDropdownsParsesCorrectly() => Block!.DropdownsEnabled.Should().BeTrue(); [Test] - public void ExplicitDropdownsRendersDropdownFormat() - { - // Should contain dropdown HTML structure - Html.Should().Contain("
    "); - Html.Should().Contain("dropdown-title__summary-text"); - Html.Should().Contain("Breaking API change."); - - // Should NOT contain bulleted list format - Html.Should().NotContain("
  • Breaking API change."); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); [Test] public void ExplicitDropdownsIncludesDescriptionInDropdown() => Html.Should().Contain("API has been changed to improve performance."); - - [Test] - public void ExplicitDropdownsIncludesImpactAndActionInDropdown() - { - Html.Should().Contain("Impact
    Existing API calls will fail."); - Html.Should().Contain("Action
    Update your code to use the new API endpoints."); - } } ///

    @@ -197,19 +156,30 @@ public ChangelogDropdownsWithHiddenDescriptionsTests() : base( ); [Test] - public void FlattendRenderingHidesDescriptionsButKeepsImpactAction() - { - // Should render as flattened (no dropdowns by default) - Html.Should().Contain("Breaking API change."); - Html.Should().NotContain("
    "); - - // Description should be hidden due to :description-visibility: hide-descriptions - Html.Should().NotContain("This description should be hidden."); - - // Impact and Action should still be shown in flattened format - Html.Should().Contain("Impact: Existing API calls will fail."); - Html.Should().Contain("Action: Update your code to use the new API endpoints."); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    +
      +
    • +

      Breaking API change. + #333333 +

      +

      + Impact: + Existing API calls will fail.

      +

      + Action: + Update your code to use the new API endpoints.

      +
    • +
    + """ + ); } /// @@ -252,20 +222,7 @@ public ChangelogDropdownsEnabledWithHiddenDescriptionsTests() : base( ); [Test] - public void DropdownRenderingHidesDescriptionsButKeepsImpactAction() - { - // Should render as dropdown due to explicit :dropdowns: - Html.Should().Contain("
    "); - Html.Should().Contain("Breaking API change."); - Html.Should().NotContain("
  • Breaking API change."); - - // Description should be hidden due to :description-visibility: hide-descriptions - Html.Should().NotContain("This description should be hidden."); - - // Impact and Action should still be shown in dropdown format - Html.Should().Contain("Impact
    Existing API calls will fail."); - Html.Should().Contain("Action
    Update your code to use the new API endpoints."); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } ///

    @@ -334,19 +291,7 @@ public ChangelogDropdownsWithDifferentTypesTests() : base( ); [Test] - public void DefaultRendersMixedTypesCorrectly() - { - // Regular types should render as bulleted lists (unchanged behavior) - Html.Should().Contain("Feature addition."); // Regular feature type (in
  • tags) - - // Separated types should render as flattened lists (new behavior) - no bold titles - Html.Should().Contain("Breaking API change."); - Html.Should().Contain("Known issue with search."); - Html.Should().Contain("Deprecated API."); - - // Should NOT contain dropdown HTML structure for separated types - Html.Should().NotContain("
    "); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// @@ -406,20 +351,7 @@ public ChangelogDropdownsExplicitWithDifferentTypesTests() : base( ); [Test] - public void ExplicitDropdownsRendersMixedTypesCorrectly() - { - // Regular types should still render as bulleted lists (unchanged behavior) - Html.Should().Contain("Feature addition."); // Regular feature type (in
  • tags) - - // Separated types should render as dropdowns (explicit :dropdowns:) - Html.Should().Contain("
    "); - Html.Should().Contain("Breaking API change."); - Html.Should().Contain("Known issue with search."); - - // Should NOT contain flattened format for separated types (check they're in dropdown, not flat list) - Html.Should().NotContain("
  • Breaking API change."); - Html.Should().NotContain("

  • Known issue with search."); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } ///

    @@ -462,9 +394,5 @@ public ChangelogDropdownsPlainTextTitleTests() : base( ); [Test] - public void ChangelogDropdownTitleStripsBackticksInHtml() - { - Html.Should().Contain("The ElasticAgentVersion parameter is malformed."); - Html.Should().NotContain("`ElasticAgentVersion`"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogFlattenedLinksTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogFlattenedLinksTests.cs index 66268fe47c..664c06f732 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogFlattenedLinksTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogFlattenedLinksTests.cs @@ -4,7 +4,9 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -59,11 +61,22 @@ public void FlattenedDeprecationRendersMultipleLinksWithoutOuterBrackets() } [Test] - public void FlattenedDeprecationRendersClickableLinkHtml() - { - Html.Should().Contain("href=\"https://github.com/elastic/elasticsearch/pull/268942\""); - Html.Should().Contain("href=\"https://github.com/elastic/elasticsearch/pull/202446\""); - Html.Should().Contain("href=\"https://github.com/elastic/elasticsearch/issues/199001\""); - Html.Should().NotContain("[#268942, #202446]"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + + """ + ); } diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogHideLinksTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogHideLinksTests.cs index 73c2bd7cd5..d340190b8b 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogHideLinksTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogHideLinksTests.cs @@ -5,8 +5,10 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; using Elastic.Documentation.Configuration.ReleaseNotes; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; using Markdig.Syntax; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -141,20 +143,29 @@ public void LoadPrivateRepositories_IncludesSkipTruePrivateRepos() } [Test] - public void RendersPrLinksForPublicRepo() - { - // elasticsearch is a public repo, so PR link should be visible in the output - Html.Should().Contain("123456"); - Html.Should().Contain("github.com"); - } - - [Test] - public void RendersIssueLinksForPublicRepo() - { - // elasticsearch is public, so issue links should be visible - Html.Should().Contain("78901"); - Html.Should().Contain("78902"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + + + """ + ); } /// diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogHighlightsOptionTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogHighlightsOptionTests.cs index ac0db68c88..0a6a0709ff 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogHighlightsOptionTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogHighlightsOptionTests.cs @@ -4,7 +4,9 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -76,14 +78,7 @@ public ChangelogHighlightsOptionDefaultOffTests() : base( public void OmitsHighlightsSection() => Html.Should().NotContain("Highlights"); [Test] - public void StillRendersHighlightedEntryUnderTypeSection() - { - Html.Should().Contain("Features and enhancements"); - Html.Should().Contain("Highlighted feature"); - Html.Should().Contain("Regular feature"); - Html.Should().Contain("Fixes"); - Html.Should().Contain("Bug fix"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); [Test] public void ExcludesSeparatedTypesByDefault() => Html.Should().NotContain("Breaking changes"); @@ -116,12 +111,7 @@ public ChangelogHighlightsOptionEnabledTests() : base( public void RendersHighlightsSection() => Html.Should().Contain("Highlights"); [Test] - public void DuplicatesHighlightedEntryInTypeSection() - { - Html.Should().Contain("Features and enhancements"); - Html.Should().Contain("Highlighted feature"); - Html.Should().Contain("Regular feature"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); [Test] public void ExcludesSeparatedTypesWithoutTypeAll() => Html.Should().NotContain("Breaking changes"); @@ -153,12 +143,7 @@ public ChangelogHighlightsOptionWithTypeAllTests() : base( ) => FileSystem.AddFile("docs/changelog/bundles/9.3.0.yaml", new MockFileData(ChangelogHighlightsFixtures.BundleYaml)); [Test] - public void RendersHighlightsAndSeparatedTypes() - { - Html.Should().Contain("Highlights"); - Html.Should().Contain("Breaking changes"); - Html.Should().Contain("Features and enhancements"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// :type: all without :highlights: — no Highlights section (breaking change from prior All behavior). @@ -216,12 +201,7 @@ public void EmitsWarningPointingToHighlightsOption() => ); [Test] - public void RendersDefaultTypeSectionsNotHighlightsOnly() - { - Html.Should().Contain("Features and enhancements"); - Html.Should().Contain("Highlighted feature"); - Html.Should().NotContain("id=\"elasticsearch-9.3.0-highlights\""); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// :highlights: + :description-visibility: keep-descriptions shows bodies in the Highlights section. @@ -298,12 +278,7 @@ public void ParsesKeepHighlightDescriptions() => public void HidesDescriptionsInTypeSections() => Html.Should().NotContain("This is the regular feature description."); [Test] - public void StillRendersTitlesInTypeSections() - { - Html.Should().Contain("Features and enhancements"); - Html.Should().Contain("Highlighted feature"); - Html.Should().Contain("Regular feature"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// keep-highlight-descriptions without :highlights: hides descriptions everywhere. @@ -352,16 +327,28 @@ public ChangelogKeepHighlightDescriptionsWithoutHighlightsTests() : base( public void OmitsHighlightsSection() => Html.Should().NotContain("id=\"elasticsearch-9.3.0-highlights\""); [Test] - public void HidesAllRecordDescriptions() - { - Html.Should().NotContain("This is the highlight description."); - Html.Should().NotContain("This is the regular feature description."); - } - - [Test] - public void StillRendersTitles() - { - Html.Should().Contain("Highlighted feature"); - Html.Should().Contain("Regular feature"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + + + """ + ); } diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogMergeTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogMergeTests.cs index a4e64b082d..19adb78cab 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogMergeTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogMergeTests.cs @@ -4,7 +4,9 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -159,13 +161,7 @@ public void RendersOnlyOneVersionHeaderPerMergedTarget() } [Test] - public void RendersAllEntriesFromMergedBundles() - { - Html.Should().Contain("Kibana feature for August 5th"); - Html.Should().Contain("Elasticsearch feature for August 5th"); - Html.Should().Contain("Elasticsearch bugfix for August 5th"); - Html.Should().Contain("Serverless feature for August 5th"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); [Test] public void MaintainsCorrectDateOrder() @@ -274,12 +270,7 @@ public void KeepsDifferentTargetsSeparate() } [Test] - public void RendersAllVersionsSeparately() - { - Html.Should().Contain("9.3.0"); - Html.Should().Contain("9.2.0"); - Html.Should().Contain("9.1.0"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); [Test] public void MaintainsSemverOrder() @@ -343,11 +334,7 @@ public ChangelogMergeSingleBundleTests() : base( public void SingleBundleHasAllEntries() => Block!.LoadedBundles[0].Entries.Should().HaveCount(2); [Test] - public void SingleBundleRendersCorrectly() - { - Html.Should().Contain("Feature in 9.3.0"); - Html.Should().Contain("Bug fix in 9.3.0"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// @@ -415,9 +402,5 @@ public void MixedVersionTypesRemainSeparate() } [Test] - public void RendersAllVersions() - { - Html.Should().Contain("9.3.0"); - Html.Should().Contain("August 5, 2025"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogPathResolutionTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogPathResolutionTests.cs index 26f79206b0..8ef5a206d1 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogPathResolutionTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogPathResolutionTests.cs @@ -4,7 +4,9 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -159,12 +161,7 @@ public ChangelogConfigRelativePathTests() : base( public void PublishBlockerIsNull() => Block!.PublishBlocker.Should().BeNull(); [Test] - public void RendersAllEntries_NoFiltering() - { - // Directive does not apply rules.publish; all entries are shown - Html.Should().Contain("Feature entry"); - Html.Should().Contain("Blocked entry"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -226,12 +223,30 @@ public ChangelogConfigDocsetRootRelativePathTests() : base( public void PublishBlockerIsNull() => Block!.PublishBlocker.Should().BeNull(); [Test] - public void RendersAllEntries_NoFiltering() - { - // Directive does not apply rules.publish; all entries are shown - Html.Should().Contain("Regular feature"); - Html.Should().Contain("Internal feature"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 1.0.0 +

    +
    + +
      +
    • Regular feature. + #33333 +
    • +
    • Internal feature. + #44444 +
    • +
    + """ + ); } [InheritsTests] @@ -382,10 +397,5 @@ public void BothPathsResolveCorrectly() } [Test] - public void RendersAllEntries_NoFiltering() - { - // Directive does not apply rules.publish; all entries are shown - Html.Should().Contain("Combined feature"); - Html.Should().Contain("Blocked by config"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogSubsectionsTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogSubsectionsTests.cs index a3b92d57e3..94fba8a3cf 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogSubsectionsTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogSubsectionsTests.cs @@ -4,7 +4,9 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -53,20 +55,30 @@ public ChangelogSubsectionsDisabledByDefaultTests() : base( public void SubsectionsPropertyDefaultsToFalse() => Block!.Subsections.Should().BeFalse(); [Test] - public void DoesNotRenderAreaHeaders() - { - // When subsections is false, area headers should not be rendered - Html.Should().NotContain("Search"); - Html.Should().NotContain("Indexing"); - } - - [Test] - public void RendersEntriesWithoutGrouping() - { - // Both entries should be rendered without area grouping - Html.Should().Contain("Feature in Search"); - Html.Should().Contain("Feature in Indexing"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + +
      +
    • Feature in Search. + #111111 +
    • +
    • Feature in Indexing. + #222222 +
    • +
    + """ + ); } [InheritsTests] @@ -115,20 +127,7 @@ public ChangelogSubsectionsEnabledTests() : base( public void SubsectionsPropertyIsTrue() => Block!.Subsections.Should().BeTrue(); [Test] - public void RendersAreaHeaders() - { - // When subsections is true, area headers should be rendered - Html.Should().Contain("Search"); - Html.Should().Contain("Indexing"); - } - - [Test] - public void RendersEntriesUnderCorrectAreas() - { - // Both entries should be rendered - Html.Should().Contain("Feature in Search"); - Html.Should().Contain("Feature in Indexing"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -214,10 +213,28 @@ public ChangelogSubsectionsNoAreaRulesTests() : base( ); [Test] - public void GroupsUnderFirstArea() - { - // No publish rules with areas → use first area - Html.Should().Contain("Search"); - Html.Should().Contain("Multi-area feature"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + +

    + Search +

    +
      +
    • Multi-area feature. + #111111 +
    • +
    + """ + ); } diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogTocFilteringTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogTocFilteringTests.cs index ec439f77bf..27a8bc4cdd 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogTocFilteringTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogTocFilteringTests.cs @@ -4,7 +4,9 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -119,12 +121,7 @@ public void AnchorsIncludeOtherSection() } [Test] - public void HtmlContainsAllSections() - { - Html.Should().Contain("Documentation"); - Html.Should().Contain("Other changes"); - Html.Should().Contain("Features and enhancements"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// @@ -211,13 +208,27 @@ public void AnchorsRetainVisibleFeaturesSection() } [Test] - public void HtmlMatchesTocFiltering() - { - Html.Should().NotContain("Other changes"); - Html.Should().NotContain("Hidden other change"); - Html.Should().Contain("Features and enhancements"); - Html.Should().Contain("Visible feature"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + + + """ + ); } /// @@ -278,11 +289,27 @@ public void AnchorsRetainSectionWhenSomeEntriesRemain() } [Test] - public void HtmlShowsVisibleEntryOnly() - { - Html.Should().Contain("Visible other change"); - Html.Should().NotContain("Hidden other change"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    +
    +

    + Other changes +

    +
    +
      +
    • Visible other change. + #111111 +
    • +
    + """ + ); } /// @@ -383,15 +410,7 @@ public void AnchorsExcludeOnlyHideFeatureFilteredSection() } [Test] - public void HtmlMatchesTocAndAnchors() - { - Html.Should().Contain("Features and enhancements"); - Html.Should().Contain("Visible feature"); - Html.Should().Contain("Documentation"); - Html.Should().Contain("Docs entry blocked by type"); - Html.Should().NotContain(">Fixes<"); - Html.Should().NotContain("Bug fix hidden by feature"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogTypeFilterTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogTypeFilterTests.cs index 9b59b69299..bc85e7b80a 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogTypeFilterTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogTypeFilterTests.cs @@ -4,7 +4,10 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -87,39 +90,7 @@ public ChangelogTypeFilterDefaultTests() : base( public void DefaultBehaviorExcludesSeparatedTypes() => Block!.TypeFilter.Should().Be(ChangelogTypeFilter.Default); [Test] - public void DefaultBehaviorShowsFeatures() - { - Html.Should().Contain("Features and enhancements"); - Html.Should().Contain("New feature"); - } - - [Test] - public void DefaultBehaviorShowsBugFixes() - { - Html.Should().Contain(">Fixes<"); - Html.Should().Contain("Bug fix"); - } - - [Test] - public void DefaultBehaviorExcludesBreakingChanges() - { - Html.Should().NotContain("Breaking changes"); - Html.Should().NotContain("Breaking API change"); - } - - [Test] - public void DefaultBehaviorExcludesKnownIssues() - { - Html.Should().NotContain("Known issues"); - Html.Should().NotContain("Known issue"); - } - - [Test] - public void DefaultBehaviorExcludesDeprecations() - { - Html.Should().NotContain("Deprecations"); - Html.Should().NotContain("Deprecated feature"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// @@ -197,19 +168,7 @@ public ChangelogTypeFilterAllTests() : base( public void TypeFilterIsAll() => Block!.TypeFilter.Should().Be(ChangelogTypeFilter.All); [Test] - public void ShowsAllEntryTypes() - { - Html.Should().Contain("Features and enhancements"); - Html.Should().Contain("New feature"); - Html.Should().Contain(">Fixes<"); - Html.Should().Contain("Bug fix"); - Html.Should().Contain("Breaking changes"); - Html.Should().Contain("Breaking API change"); - Html.Should().Contain("Known issues"); - Html.Should().Contain("Known issue"); - Html.Should().Contain("Deprecations"); - Html.Should().Contain("Deprecated feature"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); [Test] public void TableOfContentsIncludesSeparatedTypeSections() @@ -293,21 +252,30 @@ public ChangelogTypeFilterBreakingChangeTests() : base( public void TypeFilterIsBreakingChange() => Block!.TypeFilter.Should().Be(ChangelogTypeFilter.BreakingChange); [Test] - public void ShowsBreakingChanges() - { - Html.Should().NotContain("### Breaking changes"); - Html.Should().NotContain(">Breaking changes<"); - Html.Should().Contain("Breaking API change"); - } - - [Test] - public void ExcludesOtherTypes() - { - Html.Should().NotContain("Features and enhancements"); - Html.Should().NotContain("New feature"); - Html.Should().NotContain("Known issues"); - Html.Should().NotContain("Known issue"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    +
      +
    • +

      Breaking API change. + #333333 +

      +

      + Impact: + Users must update.

      +

      + Action: + Follow guide.

      +
    • +
    + """ + ); } /// @@ -368,20 +336,7 @@ public ChangelogTypeFilterDeprecationTests() : base( public void TypeFilterIsDeprecation() => Block!.TypeFilter.Should().Be(ChangelogTypeFilter.Deprecation); [Test] - public void ShowsDeprecations() - { - Html.Should().NotContain("### Deprecations"); - Html.Should().NotContain(">Deprecations<"); - Html.Should().Contain("Deprecated feature"); - Html.Should().Contain("Another deprecation"); - } - - [Test] - public void ExcludesOtherTypes() - { - Html.Should().NotContain("Features and enhancements"); - Html.Should().NotContain("New feature"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// @@ -442,20 +397,7 @@ public ChangelogTypeFilterKnownIssueTests() : base( public void TypeFilterIsKnownIssue() => Block!.TypeFilter.Should().Be(ChangelogTypeFilter.KnownIssue); [Test] - public void ShowsKnownIssues() - { - Html.Should().NotContain("### Known issues"); - Html.Should().NotContain(">Known issues<"); - Html.Should().Contain("Known issue 1"); - Html.Should().Contain("Known issue 2"); - } - - [Test] - public void ExcludesOtherTypes() - { - Html.Should().NotContain("Features and enhancements"); - Html.Should().NotContain("New feature"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// @@ -509,12 +451,27 @@ public ChangelogTypeFilterInvalidTests() : base( public void EmitsWarningForInvalidValue() => Collector.Diagnostics.Should().Contain(d => d.Message.Contains("Invalid :type: value")); [Test] - public void DefaultBehaviorIsApplied() - { - Html.Should().Contain("Features and enhancements"); - Html.Should().Contain("New feature"); - Html.Should().NotContain("Breaking changes"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + + + """ + ); } /// @@ -565,11 +522,7 @@ public ChangelogTypeFilterCaseInsensitiveTests() : base( public void AcceptsUppercaseAll() => Block!.TypeFilter.Should().Be(ChangelogTypeFilter.All); [Test] - public void ShowsAllTypes() - { - Html.Should().Contain("Features and enhancements"); - Html.Should().Contain("Breaking changes"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// @@ -636,13 +589,7 @@ public void TypeFilterAndSubsectionsBothWork() } [Test] - public void ShowsAllTypesWithSubsections() - { - Html.Should().Contain("Features and enhancements"); - Html.Should().Contain("Search feature"); - Html.Should().Contain("Indexing feature"); - Html.Should().Contain("Breaking changes"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// @@ -791,8 +738,8 @@ public ChangelogTypeFilterEmptyKnownIssueTests() : base( [Test] public void OmitsEmptyVersionBlock() { - Html.Should().NotContain("There are no known issues associated with this release"); - Html.Should().NotContain("9.3.0"); + Html.Should().MatchInlineHtmlSnapshot(html: DocsHtml.Body(), expected: ""); + Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } } @@ -833,8 +780,8 @@ public ChangelogTypeFilterEmptyBreakingChangeTests() : base( [Test] public void OmitsEmptyVersionBlock() { - Html.Should().NotContain("There are no breaking changes associated with this release"); - Html.Should().NotContain("9.3.0"); + Html.Should().MatchInlineHtmlSnapshot(html: DocsHtml.Body(), expected: ""); + Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } } @@ -875,8 +822,8 @@ public ChangelogTypeFilterEmptyDeprecationTests() : base( [Test] public void OmitsEmptyVersionBlock() { - Html.Should().NotContain("There are no deprecations associated with this release"); - Html.Should().NotContain("9.3.0"); + Html.Should().MatchInlineHtmlSnapshot(html: DocsHtml.Body(), expected: ""); + Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } } @@ -919,8 +866,8 @@ public ChangelogTypeFilterEmptyDefaultTests() : base( [Test] public void OmitsEmptyVersionBlock() { - Html.Should().NotContain("No new features, enhancements, or fixes"); - Html.Should().NotContain("9.3.0"); + Html.Should().MatchInlineHtmlSnapshot(html: DocsHtml.Body(), expected: ""); + Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } } @@ -954,8 +901,8 @@ public ChangelogTypeFilterEmptyAllTests() : base( [Test] public void OmitsEmptyVersionBlock() { - Html.Should().NotContain("No new features, enhancements, or fixes"); - Html.Should().NotContain("9.3.0"); + Html.Should().MatchInlineHtmlSnapshot(html: DocsHtml.Body(), expected: ""); + Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } } @@ -1018,12 +965,30 @@ public ChangelogTypeFilterMixedBundlesEmptyOmissionTests() : base( } [Test] - public void RendersOnlyPopulatedVersions() - { - Html.Should().Contain("Known issue in 9.3"); - Html.Should().Contain("9.3.0"); - Html.Should().NotContain("9.2.0"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    +
      +
    • +

      Known issue in 9.3. + #444444 +

      +

      + Impact: + Some impact.

      +

      + Action: + Workaround available.

      +
    • +
    + """ + ); } /// @@ -1056,12 +1021,18 @@ This release was pulled due to critical issues. ); [Test] - public void ShowsVersionAndDescriptionWithoutPlaceholder() - { - Html.Should().Contain("9.3.0"); - Html.Should().Contain("This release was pulled due to critical issues"); - Html.Should().NotContain("No new features, enhancements, or fixes"); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    +

    This release was pulled due to critical issues.

    + """ + ); } /// @@ -1095,8 +1066,8 @@ public ChangelogEmptyBundleWithReleaseDateOnlyTests() : base( [Test] public void OmitsVersionBlockWhenOnlyReleaseDate() { - Html.Should().NotContain("9.3.0"); - Html.Should().NotContain("Released:"); + Html.Should().MatchInlineHtmlSnapshot(html: DocsHtml.Body(), expected: ""); + Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } } @@ -1139,8 +1110,8 @@ public ChangelogDedicatedPageIgnoresDescriptionTests() : base( [Test] public void OmitsVersionBlockWhenNoMatchingEntries() { - Html.Should().NotContain("9.3.0"); - Html.Should().NotContain("General release notes for this version"); + Html.Should().MatchInlineHtmlSnapshot(html: DocsHtml.Body(), expected: ""); + Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } } @@ -1197,14 +1168,5 @@ public ChangelogDedicatedPageWithSubsectionsTests() : base( ); [Test] - public void GroupsEntriesByAreaWithoutSectionHeading() - { - Html.Should().NotContain("### Deprecations"); - Html.Should().NotContain(">Deprecations<"); - Html.Should().NotContain("{dropdown}"); - Html.Should().Contain("Search"); - Html.Should().Contain("Search deprecation"); - Html.Should().Contain("Indexing"); - Html.Should().Contain("Indexing deprecation"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogVersionSortingTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogVersionSortingTests.cs index 34a1512857..7a0ffbff80 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogVersionSortingTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogVersionSortingTests.cs @@ -4,7 +4,9 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -97,20 +99,7 @@ public void RendersInDateOrderDescending() } [Test] - public void RendersAllDateVersions() - { - Html.Should().Contain("August 15, 2025"); - Html.Should().Contain("August 5, 2025"); - Html.Should().Contain("August 1, 2025"); - } - - [Test] - public void RendersEntriesForDateVersions() - { - Html.Should().Contain("August 15th feature"); - Html.Should().Contain("August 5th feature"); - Html.Should().Contain("August 1st feature"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -230,22 +219,7 @@ public void SemverVersionsAppearBeforeDates() } [Test] - public void RendersAllVersions() - { - Html.Should().Contain("9.3.0"); - Html.Should().Contain("9.2.0"); - Html.Should().Contain("August 5, 2025"); - Html.Should().Contain("July 1, 2025"); - } - - [Test] - public void RendersAllEntries() - { - Html.Should().Contain("Semver 9.3.0 feature"); - Html.Should().Contain("Semver 9.2.0 feature"); - Html.Should().Contain("Date-based feature"); - Html.Should().Contain("Earlier date feature"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// @@ -336,21 +310,7 @@ public ChangelogYearMonthVersionTests() : base( public void LoadsBundles() => Block!.LoadedBundles.Should().HaveCount(3); [Test] - public void RendersHeadingsAsMonthYear() - { - Html.Should().Contain("December 2025"); - Html.Should().Contain("October 2025"); - Html.Should().Contain("August 2025"); - } - - [Test] - public void DoesNotRenderRawYearMonthInHeadings() - { - // The raw yyyy-MM format should only appear in slugs/anchors, not in visible headings - Html.Should().NotContain(">2025-12<"); - Html.Should().NotContain(">2025-10<"); - Html.Should().NotContain(">2025-08<"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); [Test] public void RendersInDescendingMonthOrder() @@ -363,24 +323,6 @@ public void RendersInDescendingMonthOrder() idxOct.Should().BeLessThan(idxAug, "October 2025 should appear before August 2025"); } - [Test] - public void PreservesOriginalSlugsForAnchors() - { - // Anchors should use the original yyyy-MM format, not the display format - Html.Should().Contain("2025-12"); - Html.Should().Contain("2025-10"); - Html.Should().Contain("2025-08"); - } - - [Test] - public void RendersAllEntries() - { - Html.Should().Contain("December feature"); - Html.Should().Contain("October feature"); - Html.Should().Contain("August feature"); - Html.Should().Contain("August bugfix"); - } - [Test] public void TocUsesMonthYearHeadings() { @@ -463,14 +405,7 @@ public ChangelogRawVersionFallbackTests() : base( public void LoadsBundles() => Block!.LoadedBundles.Should().HaveCount(2); [Test] - public void RendersNonStandardVersions() - { - // Both non-standard versions should be rendered (sorted lexicographically) - Html.Should().Contain("release-alpha"); - Html.Should().Contain("release-beta"); - Html.Should().Contain("Alpha release feature"); - Html.Should().Contain("Beta release feature"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); [Test] public void SortsLexicographically() From 3bc199a5380543a77d498fd143d1c36ab2589faa Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 12:40:09 +0100 Subject: [PATCH 06/18] Directives tests: snapshot the rendered csv, sub-pages, stepper, storybook and table HTML The Contain and NotContain fragment tests became whole-output snapshots per class, so the tests are stricter: every old fragment was checked to appear in (or, for NotContain, be absent from) the accepted snapshot before it was accepted. Small outputs are inline snapshots, the rest are files. Co-Authored-By: Claude Sonnet 5.5 --- .../RendersHtml.verified.html | 20 +++++ .../RendersHtml.verified.html | 20 +++++ .../RendersHtml.verified.html | 4 + .../RendersHtml.verified.html | 22 +++++ .../RendersHtml.verified.html | 20 +++++ .../Directives/CsvIncludeTests.cs | 8 +- .../Directives/ListSubPagesTests.cs | 22 +++-- .../Directives/StepperTocOptionTests.cs | 84 ++++++++++++------- .../Directives/StorybookTests.cs | 12 +-- .../Directives/TableDirectiveTests.cs | 75 +++++++++++------ 10 files changed, 214 insertions(+), 73 deletions(-) create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/CsvIncludeWithHtmlBreaksTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/StepperTocFalseKeepsInternalHeadingsTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/StorybookInlineIdTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/TableDirectiveFilterableWithMatrixAndWidthsTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/TableDirectiveWithWidthsTests/RendersHtml.verified.html diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/CsvIncludeWithHtmlBreaksTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/CsvIncludeWithHtmlBreaksTests/RendersHtml.verified.html new file mode 100644 index 0000000000..bd9317b6ae --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/CsvIncludeWithHtmlBreaksTests/RendersHtml.verified.html @@ -0,0 +1,20 @@ +
    + + + + + + + + + + + + + +
    NameTerms
    OpenAI + Terms A +
    + Terms B +
    +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/StepperTocFalseKeepsInternalHeadingsTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/StepperTocFalseKeepsInternalHeadingsTests/RendersHtml.verified.html new file mode 100644 index 0000000000..ce7aa1031b --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/StepperTocFalseKeepsInternalHeadingsTests/RendersHtml.verified.html @@ -0,0 +1,20 @@ +
    +

    + Section +

    +
    +
    +
      +
    1. +
      + First +
      +
      +

      + Internal +

      +
      +

      Some content under the internal heading.

      +
    2. +
    +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/StorybookInlineIdTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/StorybookInlineIdTests/RendersHtml.verified.html new file mode 100644 index 0000000000..40302d047f --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/StorybookInlineIdTests/RendersHtml.verified.html @@ -0,0 +1,4 @@ +
    + + +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/TableDirectiveFilterableWithMatrixAndWidthsTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/TableDirectiveFilterableWithMatrixAndWidthsTests/RendersHtml.verified.html new file mode 100644 index 0000000000..0148df6ebc --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/TableDirectiveFilterableWithMatrixAndWidthsTests/RendersHtml.verified.html @@ -0,0 +1,22 @@ + +
    + + + + + + + + + + + + + + + + + +
    head ahead b
    ab
    +
    +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/TableDirectiveWithWidthsTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/TableDirectiveWithWidthsTests/RendersHtml.verified.html new file mode 100644 index 0000000000..10697c2cda --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/TableDirectiveWithWidthsTests/RendersHtml.verified.html @@ -0,0 +1,20 @@ +
    + + + + + + + + + + + + + + + + + +
    head ahead b
    ab
    +
    diff --git a/tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs b/tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs index e8462b9cee..f28b2245d4 100644 --- a/tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs @@ -6,7 +6,9 @@ using AwesomeAssertions; using Elastic.Documentation.Configuration; using Elastic.Documentation.FileSystems; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.CsvInclude; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -184,11 +186,7 @@ protected override void AddToFileSystem(MockFileSystem fileSystem) => public void RendersHtmlBreaksInCsvCells() => Html.Should().Contain("Terms A"); - Html.Should().Contain(">Terms B"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] diff --git a/tests/Elastic.Markdown.Tests/Directives/ListSubPagesTests.cs b/tests/Elastic.Markdown.Tests/Directives/ListSubPagesTests.cs index 0547fe619c..431e158975 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ListSubPagesTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ListSubPagesTests.cs @@ -4,7 +4,9 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.SubPages; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -38,12 +40,20 @@ public void SubPagesContainTitlesAndUrls() => Block!.SubPages.Should().OnlyContain(p => !string.IsNullOrEmpty(p.Title) && !string.IsNullOrEmpty(p.Url)); [Test] - public void RendersListWithLinks() - { - Html.Should().Contain("list-sub-pages"); - Html.Should().Contain(""); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ + + """ + ); } [InheritsTests] diff --git a/tests/Elastic.Markdown.Tests/Directives/StepperTocOptionTests.cs b/tests/Elastic.Markdown.Tests/Directives/StepperTocOptionTests.cs index 00ff3d9fa5..1d41add0f9 100644 --- a/tests/Elastic.Markdown.Tests/Directives/StepperTocOptionTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/StepperTocOptionTests.cs @@ -4,7 +4,9 @@ using AwesomeAssertions; using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Stepper; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -31,13 +33,22 @@ public void Toc_WhenDefault_IncludesStepTitle() } [Test] - public void Render_WhenDefault_UsesHeadingElement() - { - Html.Should().Contain(" + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +
      +
    1. +

      + Install +

      +

      First install the dependencies.

      +
    2. +
    +
    + """ + ); } [InheritsTests] @@ -62,15 +73,22 @@ public void Toc_WhenTocFalse_OmitsStepTitle() } [Test] - public void Render_WhenTocFalse_UsesDiv() - { - Html.Should().NotContain(" + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +
      +
    1. +
      + Install +
      +

      First install the dependencies.

      +
    2. +
    +
    + """ + ); } [InheritsTests] @@ -99,12 +117,7 @@ public void Toc_WhenTocFalse_KeepsInternalHeading() } [Test] - public void Render_WhenTocFalse_KeepsInternalHeadingLevel() - { - Html.Should().Contain(" Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -140,10 +153,25 @@ public void Hint_WhenNoPrecedingHeading_NamesOutlineLevel() } [Test] - public void Render_WhenNoPrecedingHeading_AdjustsInternalHeading() - { - Html.Should().Contain(" + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +
      +
    1. +
      + First +
      +
      +

      + Too high +

      +
      +

      Body.

      +
    2. +
    +
    + """ + ); } diff --git a/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs b/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs index 13731bb2d2..c4df85a2f1 100644 --- a/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs @@ -6,7 +6,9 @@ using AwesomeAssertions; using Elastic.Documentation; using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Storybook; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -104,15 +106,7 @@ public void ResolvesStory() } [Test] - public void RendersInlineStory() - { - Html.Should().Contain(" Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// Deterministic so storybook interpolation tests don't depend on the host shell. diff --git a/tests/Elastic.Markdown.Tests/Directives/TableDirectiveTests.cs b/tests/Elastic.Markdown.Tests/Directives/TableDirectiveTests.cs index 9db847b13d..b624adbbb4 100644 --- a/tests/Elastic.Markdown.Tests/Directives/TableDirectiveTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/TableDirectiveTests.cs @@ -4,7 +4,9 @@ using AwesomeAssertions; using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Table; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -26,13 +28,28 @@ public class TableDirectiveBasicTests() : DirectiveTest( public void SetsCorrectDirectiveType() => Block!.Directive.Should().Be("table"); [Test] - public void RendersTableInOutput() - { - Html.Should().Contain("table-wrapper"); - Html.Should().Contain(" + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    + + + + + + + + + + + + + +
    head ahead b
    ab
    +
    + """ + ); } [InheritsTests] @@ -57,13 +74,7 @@ public void ParsesWidthsOption() } [Test] - public void RendersColgroupWithWidths() - { - Html.Should().Contain("colgroup"); - Html.Should().Contain("table-layout:fixed"); - Html.Should().Contain("width:33.33%"); - Html.Should().Contain("width:66.67%"); - } + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -163,11 +174,30 @@ public class TableDirectiveFilterableTests() : DirectiveTest Block!.Filterable.Should().BeTrue(); [Test] - public void WrapsTableInHostElement() - { - Html.Should().Contain(""); - Html.Should().Contain(""); - } + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ + +
    + + + + + + + + + + + + + +
    head ahead b
    ab
    +
    +
    + """ + ); [Test] public void KeepsServerRenderedTableInsideHost() => Html.Should().Contain("
    Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] From 4ae81b698a259029385535d0111a9d5c98fb06fd Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 12:43:52 +0100 Subject: [PATCH 07/18] Directives tests: finish the agent skill HTML snapshots The remaining single-fragment Contain and NotContain tests of these classes became the class's whole-output snapshot, so the tests are stricter: every old fragment was checked to appear in (or, for NotContain, be absent from) the accepted snapshot before it was accepted. Co-Authored-By: Claude Sonnet 5.5 --- .../RendersHtml.verified.html | 24 +++++++++++++++++++ .../Directives/AgentSkillTests.cs | 20 +--------------- 2 files changed, 25 insertions(+), 19 deletions(-) create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/AgentSkillWithBodyTests/RendersHtml.verified.html diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/AgentSkillWithBodyTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/AgentSkillWithBodyTests/RendersHtml.verified.html new file mode 100644 index 0000000000..688457a4e6 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/AgentSkillWithBodyTests/RendersHtml.verified.html @@ -0,0 +1,24 @@ +
    +
    + + + + Agent skill available +
    +
    +
    +

    A skill is available to help AI agents with this topic.

    +

    This skill helps agents write and optimize ES|QL queries.

    +

    + Learn more about agent skills for Elastic +

    +
    + +
    +
    +

    A regular paragraph.

    diff --git a/tests/Elastic.Markdown.Tests/Directives/AgentSkillTests.cs b/tests/Elastic.Markdown.Tests/Directives/AgentSkillTests.cs index e8c961a86f..fede4039c1 100644 --- a/tests/Elastic.Markdown.Tests/Directives/AgentSkillTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/AgentSkillTests.cs @@ -36,15 +36,6 @@ A regular paragraph. [Test] public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); - - [Test] - public void RendersTitle() => Html.Should().Contain("Agent skill available"); - - [Test] - public void RendersDefaultText() => Html.Should().Contain("A skill is available to help AI agents with this topic."); - - [Test] - public void DoesNotRenderLinkButton() => Html.Should().NotContain("Get the skill"); } [InheritsTests] @@ -60,16 +51,7 @@ A regular paragraph. ) { [Test] - public void RendersCustomBody() => Html.Should().Contain("This skill helps agents write and optimize ES|QL queries."); - - [Test] - public void StillRendersDefaultText() => Html.Should().Contain("A skill is available to help AI agents with this topic."); - - [Test] - public void StillRendersLearnMoreLink() => Html.Should().Contain("Learn more about agent skills for Elastic"); - - [Test] - public void StillRendersCopyButton() => Html.Should().Contain("Copy install command"); + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] From 75957d7632a3aa3ff6fc66c37292ef25633ed120 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 12:43:56 +0100 Subject: [PATCH 08/18] Directives tests: finish the changelog HTML snapshots The remaining single-fragment Contain and NotContain tests of these classes became the class's whole-output snapshot, so the tests are stricter: every old fragment was checked to appear in (or, for NotContain, be absent from) the accepted snapshot before it was accepted. The NotContain-only tests of changelog output that ends up empty also assert an empty diagnostics list now. Co-Authored-By: Claude Sonnet 5.5 --- .../RendersHtml.verified.html | 43 ++++++ .../Directives/ChangelogBasicTests.cs | 133 +++++++++++++++--- .../Directives/ChangelogConfigTests.cs | 75 ++++++++-- .../ChangelogDescriptionVisibilityTests.cs | 88 +++++++++++- .../Directives/ChangelogDropdownsTests.cs | 3 - .../ChangelogHighlightsOptionTests.cs | 23 +-- .../ChangelogPathResolutionTests.cs | 88 +++++++++++- .../ChangelogReleaseDatesOptionTests.cs | 100 +++++++++++-- .../Directives/ChangelogSubsectionsTests.cs | 22 ++- .../Directives/ChangelogTocFilteringTests.cs | 7 +- .../ChangelogVersionVisibilityTests.cs | 24 +++- 11 files changed, 525 insertions(+), 81 deletions(-) create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionWithDescriptionsTests/RendersHtml.verified.html diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionWithDescriptionsTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionWithDescriptionsTests/RendersHtml.verified.html new file mode 100644 index 0000000000..7a952f6693 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionWithDescriptionsTests/RendersHtml.verified.html @@ -0,0 +1,43 @@ +
    +

    + 9.3.0 +

    +
    +
    +

    + Highlights +

    +
    +
      +
    • Highlighted feature. + #111111 + This is the highlight description.
    • +
    + +
      +
    • +

      Highlighted feature. + #111111 +

      +

      This is the highlight description.

      +
    • +
    • +

      Regular feature. + #222222 +

      +
    • +
    +
    +

    + Fixes +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs index 2301ba55e6..f144f003fb 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs @@ -1184,13 +1184,28 @@ public ChangelogTitleDescriptionSpacingTests() : base( ); [Test] - public void RendersTitleText() => Html.Should().Contain("Added missing banner-related Kibana settings to the settings allowlist"); - - [Test] - public void RendersDescriptionText() => Html.Should().Contain("This PR introduces the following settings"); - - [Test] - public void DoesNotConcatenateTitleAndDescriptionWithoutSeparator() => Html.Should().NotContain("allowlist.This PR introduces"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + +
      +
    • +

      Added missing banner-related Kibana settings to the settings allowlist.

      +

      This PR introduces the following settings.

      +
    • +
    + """ + ); } /// @@ -1229,10 +1244,30 @@ public ChangelogReleaseDateTests() : base( ); [Test] - public void RendersReleaseDate() => Html.Should().Contain("Released: April 9, 2026"); - - [Test] - public void RendersEntries() => Html.Should().Contain("Add tracing improvements"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 1.34.0 +

    +
    +

    + Released: April 9, 2026 +

    + +
      +
    • Add tracing improvements. + #500 +
    • +
    + """ + ); } /// @@ -1269,7 +1304,27 @@ public ChangelogNoReleaseDateTests() : base( ); [Test] - public void DoesNotRenderReleaseDate() => Html.Should().NotContain("Released:"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + +
      +
    • New feature. + #100 +
    • +
    + """ + ); } /// @@ -1310,13 +1365,31 @@ This release includes tracing improvements and bug fixes. ); [Test] - public void RendersReleaseDate() => Html.Should().Contain("Released: April 9, 2026"); - - [Test] - public void RendersDescription() => Html.Should().Contain("This release includes tracing improvements and bug fixes."); - - [Test] - public void RendersEntries() => Html.Should().Contain("Add tracing improvements"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 1.34.0 +

    +
    +

    + Released: April 9, 2026 +

    +

    This release includes tracing improvements and bug fixes.

    + +
      +
    • Add tracing improvements. + #500 +
    • +
    + """ + ); } /// @@ -1361,5 +1434,25 @@ public ChangelogDoubleBraceEscapeTests() : base( public void RendersWithoutErrors() => Collector.Errors.Should().Be(0); [Test] - public void RendersTitleWithLiteralBraces() => Html.Should().Contain("{{hostname}}"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    +
    +

    + Fixes +

    +
    +
      +
    • Fix ECE API ingestion blocked by {{hostname}} template placeholder. + #123 +
    • +
    + """ + ); } diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogConfigTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogConfigTests.cs index 42ad181db4..b9ea5d3157 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogConfigTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogConfigTests.cs @@ -83,15 +83,6 @@ public ChangelogConfigLoadAutoDiscoverTests() : base( [Test] public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); - - [Test] - public void RendersFeaturesSection() => Html.Should().Contain("Features and enhancements"); - - [Test] - public void RendersDeprecationsSection() => Html.Should().Contain("Deprecations"); - - [Test] - public void RendersKnownIssuesSection() => Html.Should().Contain("Known issues"); } [InheritsTests] @@ -254,7 +245,27 @@ public ChangelogConfigNotFoundTests() : base( public void PublishBlockerIsNullWhenNoConfig() => Block!.PublishBlocker.Should().BeNull(); [Test] - public void RendersAllEntriesWhenNoConfig() => Html.Should().Contain("Regular feature"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + + + """ + ); [Test] public void NoErrorsEmittedForMissingConfig() => Collector.Diagnostics.Should().NotContain(d => d.Message.Contains("changelog.yml")); @@ -299,7 +310,27 @@ public void EmitsWarningForMissingExplicitConfig() => Collector.Diagnostics.Should().Contain(d => d.Message.Contains("nonexistent/config.yml") && d.Message.Contains("not found")); [Test] - public void RendersAllEntriesWhenConfigNotFound() => Html.Should().Contain("Regular feature"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + + + """ + ); } [InheritsTests] @@ -434,7 +465,27 @@ public ChangelogConfigEmptyBlockTests() : base( public void PublishBlockerIsNullWhenNoBlockSection() => Block!.PublishBlocker.Should().BeNull(); [Test] - public void RendersAllEntriesWhenNoBlockSection() => Html.Should().Contain("Regular feature"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + + + """ + ); } [InheritsTests] diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogDescriptionVisibilityTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogDescriptionVisibilityTests.cs index f47e201747..fcb2eca043 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogDescriptionVisibilityTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogDescriptionVisibilityTests.cs @@ -4,8 +4,10 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; using Markdig.Syntax; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -149,10 +151,25 @@ protected override void AddToFileSystem(MockFileSystem fileSystem) => /// Public bundle with no assembler private repos ⇒ auto hides record bodies. [Test] - public void HtmlOmitsBodyTextForPublicBundle() => Html.Should().NotContain("BODY_DEFAULT_AUTO_VISIBILITY"); - - [Test] - public void HtmlStillRendersTitles() => Html.Should().Contain("Feature delta"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + +
      +
    • Feature delta.
    • +
    + """ + ); } [InheritsTests] @@ -230,7 +247,28 @@ protected override void AddToFileSystem(MockFileSystem fileSystem) => ); [Test] - public void KeepsBodyOnFullyPublicRepos() => Html.Should().Contain("BODY_KEEP_VISIBILITY"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + +
      +
    • +

      Feature keep.

      +

      BODY_KEEP_VISIBILITY

      +
    • +
    + """ + ); } [InheritsTests] @@ -262,7 +300,25 @@ protected override void AddToFileSystem(MockFileSystem fileSystem) => ); [Test] - public void OmitBody() => Html.Should().NotContain("BODY_HIDE_VISIBILITY"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + +
      +
    • Feature hide.
    • +
    + """ + ); [Test] public void MarkdownRendersTitlesWithoutBodies() @@ -308,5 +364,23 @@ protected override void AddToFileSystem(MockFileSystem fileSystem) => public void EmitsWarning() => Collector.Warnings.Should().BeGreaterThan(0); [Test] - public void AutoTreatsFullyPublic_AsHideBody() => Html.Should().NotContain("BODY_INVALID_VISIBILITY"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + +
      +
    • Feature warn.
    • +
    + """ + ); } diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogDropdownsTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogDropdownsTests.cs index acb2cc35ea..2f096e28b0 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogDropdownsTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogDropdownsTests.cs @@ -112,9 +112,6 @@ public ChangelogDropdownsEnabledTests() : base( [Test] public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); - - [Test] - public void ExplicitDropdownsIncludesDescriptionInDropdown() => Html.Should().Contain("API has been changed to improve performance."); } /// diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogHighlightsOptionTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogHighlightsOptionTests.cs index 0a6a0709ff..4a9a4c3761 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogHighlightsOptionTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogHighlightsOptionTests.cs @@ -74,15 +74,9 @@ public ChangelogHighlightsOptionDefaultOffTests() : base( [Test] public void HighlightsDisabledByDefault() => Block!.HighlightsEnabled.Should().BeFalse(); - [Test] - public void OmitsHighlightsSection() => Html.Should().NotContain("Highlights"); - [Test] public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); - [Test] - public void ExcludesSeparatedTypesByDefault() => Html.Should().NotContain("Breaking changes"); - [Test] public void TocOmitsHighlights() { @@ -107,15 +101,9 @@ public ChangelogHighlightsOptionEnabledTests() : base( [Test] public void HighlightsEnabledWhenFlagPresent() => Block!.HighlightsEnabled.Should().BeTrue(); - [Test] - public void RendersHighlightsSection() => Html.Should().Contain("Highlights"); - [Test] public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); - [Test] - public void ExcludesSeparatedTypesWithoutTypeAll() => Html.Should().NotContain("Breaking changes"); - [Test] public void TocIncludesHighlights() { @@ -219,7 +207,7 @@ public ChangelogHighlightsOptionWithDescriptionsTests() : base( ) => FileSystem.AddFile("docs/changelog/bundles/9.3.0.yaml", new MockFileData(ChangelogHighlightsFixtures.BundleYaml)); [Test] - public void ShowsDescriptionInHighlightsSection() => Html.Should().Contain("This is the highlight description."); + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } /// @@ -271,12 +259,6 @@ public ChangelogKeepHighlightDescriptionsTests() : base( public void ParsesKeepHighlightDescriptions() => Block!.DescriptionVisibility.Should().Be(ChangelogDescriptionVisibility.KeepHighlightDescriptions); - [Test] - public void ShowsDescriptionInHighlightsSection() => Html.Should().Contain("This is the highlight description."); - - [Test] - public void HidesDescriptionsInTypeSections() => Html.Should().NotContain("This is the regular feature description."); - [Test] public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } @@ -323,9 +305,6 @@ public ChangelogKeepHighlightDescriptionsWithoutHighlightsTests() : base( ) ); - [Test] - public void OmitsHighlightsSection() => Html.Should().NotContain("id=\"elasticsearch-9.3.0-highlights\""); - [Test] public void RendersHtml() => Html.Should().MatchInlineHtmlSnapshot( diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogPathResolutionTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogPathResolutionTests.cs index 8ef5a206d1..869da4ff72 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogPathResolutionTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogPathResolutionTests.cs @@ -53,7 +53,27 @@ public void PathCombinedWithDocsetRoot() => Block!.BundlesFolderPath.Should().EndWith("custom/path/bundles".Replace('/', Path.DirectorySeparatorChar)); [Test] - public void RendersContent() => Html.Should().Contain("Test feature"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 1.0.0 +

    +
    + +
      +
    • Test feature. + #12345 +
    • +
    + """ + ); } [InheritsTests] @@ -97,7 +117,27 @@ public void SlashPrefixIsTrimmed() => public void PathDoesNotContainDoubleSlashes() => Block!.BundlesFolderPath.Should().NotContain("//"); [Test] - public void RendersContent() => Html.Should().Contain("Another feature"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 2.0.0 +

    +
    + +
      +
    • Another feature. + #67890 +
    • +
    + """ + ); } [InheritsTests] @@ -283,7 +323,27 @@ public ChangelogBundlesFolderNestedRelativePathTests() : base( public void ResolvesDeepNestedPath() => Block!.Found.Should().BeTrue(); [Test] - public void RendersContent() => Html.Should().Contain("Nested feature"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 3.0.0 +

    +
    + +
      +
    • Nested feature. + #99999 +
    • +
    + """ + ); } /// @@ -333,7 +393,27 @@ public ChangelogPathEdgeCaseTests() : base( public void ResolvesSlashPrefixedPath() => Block!.Found.Should().BeTrue(); [Test] - public void RendersContent() => Html.Should().Contain("Edge case feature"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 1.0.0 +

    +
    + +
      +
    • Edge case feature. + #55555 +
    • +
    + """ + ); } [InheritsTests] diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogReleaseDatesOptionTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogReleaseDatesOptionTests.cs index 2441e7d574..57346b084d 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogReleaseDatesOptionTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogReleaseDatesOptionTests.cs @@ -4,7 +4,9 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -44,10 +46,27 @@ public ChangelogReleaseDatesOptionDefaultOffTests() : base( public void ReleaseDatesDisabledByDefault() => Block!.ReleaseDatesEnabled.Should().BeFalse(); [Test] - public void OmitsReleasedLineWhenFlagOmitted() => Html.Should().NotContain("Released:"); - - [Test] - public void StillRendersEntries() => Html.Should().Contain("Add tracing improvements"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 1.34.0 +

    +
    + +
      +
    • Add tracing improvements. + #500 +
    • +
    + """ + ); } [InheritsTests] @@ -86,7 +105,30 @@ public ChangelogReleaseDatesOptionEnabledTests() : base( public void ReleaseDatesEnabledWhenFlagPresent() => Block!.ReleaseDatesEnabled.Should().BeTrue(); [Test] - public void RendersReleasedLineWhenBundleHasReleaseDate() => Html.Should().Contain("Released: April 9, 2026"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 1.34.0 +

    +
    +

    + Released: April 9, 2026 +

    + +
      +
    • Add tracing improvements. + #500 +
    • +
    + """ + ); } [InheritsTests] @@ -121,7 +163,27 @@ public ChangelogReleaseDatesOptionEnabledWithoutBundleDateTests() : base( ); [Test] - public void OmitsReleasedLineWhenBundleHasNoReleaseDate() => Html.Should().NotContain("Released:"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + +
      +
    • New feature. + #100 +
    • +
    + """ + ); } [InheritsTests] @@ -158,8 +220,26 @@ This release includes tracing improvements and bug fixes. ); [Test] - public void OmitsReleasedLineWhenFlagOmitted() => Html.Should().NotContain("Released:"); - - [Test] - public void RendersBundleDescription() => Html.Should().Contain("This release includes tracing improvements and bug fixes."); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 1.34.0 +

    +
    +

    This release includes tracing improvements and bug fixes.

    + +
      +
    • Add tracing improvements. + #500 +
    • +
    + """ + ); } diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogSubsectionsTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogSubsectionsTests.cs index 94fba8a3cf..a88fb7e1fd 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogSubsectionsTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogSubsectionsTests.cs @@ -167,7 +167,27 @@ public ChangelogSubsectionsExplicitFalseTests() : base( public void SubsectionsPropertyIsFalse() => Block!.Subsections.Should().BeFalse(); [Test] - public void DoesNotRenderAreaHeaders() => Html.Should().NotContain("Search"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.3.0 +

    +
    + +
      +
    • Feature in Search. + #111111 +
    • +
    + """ + ); } /// diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogTocFilteringTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogTocFilteringTests.cs index 27a8bc4cdd..329d80da77 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogTocFilteringTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogTocFilteringTests.cs @@ -6,6 +6,7 @@ using AwesomeAssertions; using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots; using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -568,7 +569,11 @@ public void OmitsVersionFromTocWhenNoRenderableEntries() } [Test] - public void OmitsVersionFromRenderedOutput() => Html.Should().NotContain("9.3.0"); + public void OmitsVersionFromRenderedOutput() + { + Html.Should().MatchInlineHtmlSnapshot(html: DocsHtml.Body(), expected: ""); + Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); + } [Test] public void NoAnchorsGenerated() diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogVersionVisibilityTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogVersionVisibilityTests.cs index 3932e94f97..7d4bb5adc8 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogVersionVisibilityTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogVersionVisibilityTests.cs @@ -6,7 +6,9 @@ using Elastic.Documentation.Configuration.Assembler; using Elastic.Documentation.Configuration.ReleaseNotes; using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -152,5 +154,25 @@ protected override IReleaseNotesResolver GetReleaseNotesResolver() => ); [Test] - public void DoesNotFilterProductsWithoutVersioningSystem() => Html.Should().Contain("Future-looking change"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.9.0 +

    +
    + +
      +
    • Future-looking change. + #300 +
    • +
    + """ + ); } From 626c2f6427d4e5219864ec7f381c581ad4e0e061 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 12:43:58 +0100 Subject: [PATCH 09/18] Directives tests: snapshot the rendered button, mermaid, math, vector sizing and applicability switch HTML The remaining single-fragment Contain and NotContain tests of these classes became the class's whole-output snapshot, so the tests are stricter: every old fragment was checked to appear in (or, for NotContain, be absent from) the accepted snapshot before it was accepted. Mermaid snapshots scrub the diagram file name hash, which tracks the renderer output rather than the markup. Co-Authored-By: Claude Sonnet 5.5 --- .../RendersHtml.verified.html | 50 +++++ .../RendersHtml.verified.html | 20 ++ .../Directives/ApplicabilitySwitchTests.cs | 6 +- .../Directives/ButtonTests.cs | 212 ++++++++++++++---- .../Directives/MathBlockTests.cs | 23 +- .../Directives/MermaidTests.cs | 148 +++++++++--- .../Directives/VectorSizingBlockTests.cs | 5 +- 7 files changed, 393 insertions(+), 71 deletions(-) create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ApplicabilitySwitchTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ApplicabilitySwitchWithCommentTests/RendersHtml.verified.html diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ApplicabilitySwitchTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ApplicabilitySwitchTests/RendersHtml.verified.html new file mode 100644 index 0000000000..425255841a --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ApplicabilitySwitchTests/RendersHtml.verified.html @@ -0,0 +1,50 @@ +
    + + +
    +
    +
    + Tip +
    +
    +

    This feature is in preview for Elastic Stack 9.1.

    +
    +
    +
    + + +
    +
    +
    + Note +
    +
    +

    This feature is available for Elastic Cloud.

    +
    +
    +
    + + +
    +
    +
    + Warning +
    +
    +

    This feature has been removed from Elastic Cloud Enterprise.

    +
    +
    +
    +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ApplicabilitySwitchWithCommentTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ApplicabilitySwitchWithCommentTests/RendersHtml.verified.html new file mode 100644 index 0000000000..d5ea0d5208 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ApplicabilitySwitchWithCommentTests/RendersHtml.verified.html @@ -0,0 +1,20 @@ +
    + + +
    +

    Content A

    +
    + + +
    +

    Content B

    +
    +
    diff --git a/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchTests.cs b/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchTests.cs index 286f00ac9a..b886a5f642 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchTests.cs @@ -3,7 +3,9 @@ // See the LICENSE file in the project root for more information using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.AppliesSwitch; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -47,7 +49,7 @@ public void ParsesApplicabilitySwitchItems() } [Test] - public void FirstItemRendersChecked() => Html.Should().Contain("applies-switch-input\" checked=\"checked\""); + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); [Test] public void ParsesAppliesToDefinitions() @@ -99,7 +101,7 @@ public void ItemIndicesAreZeroBased() } [Test] - public void FirstItemRendersChecked() => Html.Should().Contain("applies-switch-input\" checked=\"checked\""); + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] diff --git a/tests/Elastic.Markdown.Tests/Directives/ButtonTests.cs b/tests/Elastic.Markdown.Tests/Directives/ButtonTests.cs index 6702e1ade7..1777b66367 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ButtonTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ButtonTests.cs @@ -3,7 +3,9 @@ // See the LICENSE file in the project root for more information using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Button; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -24,13 +26,17 @@ [Get Started](/get-started) public void DefaultsToLeftAlign() => Block!.Align.Should().Be("left"); [Test] - public void RendersPrimaryButtonClass() => Html.Should().Contain("doc-button-primary"); - - [Test] - public void RendersLinkHref() => Html.Should().Contain("href=\"/get-started\""); - - [Test] - public void RendersButtonText() => Html.Should().Contain("Get Started"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + Get Started +

    +
    + """ + ); } [InheritsTests] @@ -45,7 +51,17 @@ [Learn More](/learn-more) public void ParsesSecondaryType() => Block!.Type.Should().Be("secondary"); [Test] - public void RendersSecondaryClass() => Html.Should().Contain("doc-button-secondary"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + Learn More +

    +
    + """ + ); } [InheritsTests] @@ -62,7 +78,17 @@ [Browse All Docs](https://www.elastic.co/docs) public void ParsesNeutralType() => Block!.Type.Should().Be("neutral"); [Test] - public void RendersNeutralClass() => Html.Should().Contain("doc-button-neutral"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + Browse All Docs +

    +
    + """ + ); [Test] public void EmitsNoErrors() => Collector.Diagnostics.Should().BeEmpty(); @@ -98,10 +124,24 @@ [Get Started](/get-started) ) { [Test] - public void RendersNeutralItemInsideGroup() => Html.Should().Contain("class=\"doc-button-item doc-button-neutral\""); - - [Test] - public void RendersPrimaryAlongsideNeutral() => Html.Should().Contain("doc-button-primary"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    + +

    + Get Started +

    +
    + +

    + What's New +

    +
    +
    + """ + ); } [InheritsTests] @@ -116,7 +156,17 @@ [Centered Button](/centered) public void ParsesCenterAlign() => Block!.Align.Should().Be("center"); [Test] - public void RendersWrapperWithAlignClass() => Html.Should().Contain("doc-button-wrapper doc-button-primary doc-button-center"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + Centered Button +

    +
    + """ + ); } [InheritsTests] @@ -127,10 +177,17 @@ public class ButtonExternalTests() : DirectiveTest(""" """) { [Test] - public void RendersExternalAttributes() => Html.Should().Contain("target=\"_blank\""); - - [Test] - public void RendersNoopenerNoreferrer() => Html.Should().Contain("rel=\"noopener noreferrer\""); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + GitHub +

    +
    + """ + ); } [InheritsTests] @@ -145,10 +202,17 @@ public class ButtonReferenceLinkTests() : DirectiveTest( ) { [Test] - public void RendersReferencedLinkHref() => Html.Should().Contain("href=\"https://foo.example.com\""); - - [Test] - public void RendersButtonText() => Html.Should().Contain(">Open<"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + Open +

    +
    + """ + ); [Test] public void EmitsNoErrors() => Collector.Diagnostics.Should().BeEmpty(); @@ -190,16 +254,27 @@ public class ButtonGroupTests() : DirectiveTest( public void ParsesButtonGroup() => Block.Should().NotBeNull(); [Test] - public void RendersButtonGroupContainer() => Html.Should().Contain("class=\"doc-button-group"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    + +

    + Primary +

    +
    + +

    + Secondary +

    +
    +
    + """ + ); [Test] public void ContainsBothButtons() => Html.Should().Contain("Primary").And.Contain("Secondary"); - - [Test] - public void RendersPrimaryButton() => Html.Should().Contain("doc-button-primary"); - - [Test] - public void RendersSecondaryButton() => Html.Should().Contain("doc-button-secondary"); } [InheritsTests] @@ -218,7 +293,19 @@ public class ButtonGroupAlignmentTests() : DirectiveTest( public void ParsesGroupAlignment() => Block!.Align.Should().Be("center"); [Test] - public void RendersGroupAlignClass() => Html.Should().Contain("doc-button-group-center"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    + +

    + Centered +

    +
    +
    + """ + ); } [InheritsTests] @@ -234,10 +321,19 @@ [In Group](/in-group) public void DetectsButtonIsInGroup() => Block!.IsInGroup.Should().BeTrue(); [Test] - public void DoesNotRenderWrapperInGroup() => Html.Should().NotContain("doc-button-wrapper"); - - [Test] - public void RendersButtonItem() => Html.Should().Contain("doc-button-item"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    + +

    + In Group +

    +
    +
    + """ + ); } [InheritsTests] @@ -251,7 +347,17 @@ [Kibana Docs](kibana://api/index.md) public void ParsesBlock() => Block.Should().NotBeNull(); [Test] - public void RendersLinkHref() => Html.Should().Contain("href=\""); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + Kibana Docs +

    +
    + """ + ); } [InheritsTests] @@ -267,7 +373,17 @@ [Install with Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=elasti public void ParsesBlock() => Block.Should().NotBeNull(); [Test] - public void RendersLinkHref() => Html.Should().Contain("href=\"cursor://"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ + + """ + ); [Test] public void EmitsNoErrors() => Collector.Diagnostics.Should().BeEmpty(); @@ -286,7 +402,17 @@ [Install with VS Code](vscode:extension/elastic.elasticsearch) public void ParsesBlock() => Block.Should().NotBeNull(); [Test] - public void RendersLinkHref() => Html.Should().Contain("href=\"vscode:"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ + + """ + ); [Test] public void EmitsNoErrors() => Collector.Diagnostics.Should().BeEmpty(); @@ -305,7 +431,17 @@ [Install with VS Code Insiders](vscode-insiders:mcp/install?%7B%22name%22%3A%22o public void ParsesBlock() => Block.Should().NotBeNull(); [Test] - public void RendersLinkHref() => Html.Should().Contain("href=\"vscode-insiders:"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ + + """ + ); [Test] public void EmitsNoErrors() => Collector.Diagnostics.Should().BeEmpty(); diff --git a/tests/Elastic.Markdown.Tests/Directives/MathBlockTests.cs b/tests/Elastic.Markdown.Tests/Directives/MathBlockTests.cs index 356e2b4e0e..6fb0bcfd55 100644 --- a/tests/Elastic.Markdown.Tests/Directives/MathBlockTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/MathBlockTests.cs @@ -4,7 +4,9 @@ using AwesomeAssertions; using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Math; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -28,7 +30,8 @@ public class MathBlockTests() : DirectiveTest(""" public void DeterminesInlineMath() => Block!.IsDisplayMath.Should().BeFalse(); [Test] - public void RendersMathSpan() => Html.Should().Contain("E = mc^2"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot(html: DocsHtml.Body(), expected: "E = mc^2"); } [InheritsTests] @@ -52,7 +55,11 @@ public class MathBlockDisplayMathTests() : DirectiveTest( public void DeterminesDisplayMath() => Block!.IsDisplayMath.Should().BeTrue(); [Test] - public void RendersDisplayMathDiv() => Html.Should().Contain("
    "); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: "
    \\[ \\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi} \\]
    " + ); } [InheritsTests] @@ -70,7 +77,11 @@ public class MathBlockWithLabelTests() : DirectiveTest(""" public void ExtractsLabel() => Block!.Label.Should().Be("einstein-mass-energy"); [Test] - public void RendersWithId() => Html.Should().Contain("id=\"einstein-mass-energy\""); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: "E = mc^2" + ); } [InheritsTests] @@ -113,5 +124,9 @@ public class MathBlockComplexExpressionTests() : DirectiveTest( public void DeterminesDisplayMathFromBegin() => Block!.IsDisplayMath.Should().BeTrue(); [Test] - public void RendersComplexMathDiv() => Html.Should().Contain("
    "); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: "
    \\begin{align} \\frac{\\partial f}{\\partial x} &= \\lim_{h \\to 0} \\frac{f(x+h) - f(x)}{h} \\\\ \\nabla \\cdot \\vec{E} &= \\frac{\\rho}{\\epsilon_0} \\end{align}
    " + ); } diff --git a/tests/Elastic.Markdown.Tests/Directives/MermaidTests.cs b/tests/Elastic.Markdown.Tests/Directives/MermaidTests.cs index 44f898a355..c7b7f07b89 100644 --- a/tests/Elastic.Markdown.Tests/Directives/MermaidTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/MermaidTests.cs @@ -2,12 +2,22 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.Text.RegularExpressions; using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.CodeBlocks; using Markdig.Syntax; +using Nullean.Snapshots; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; +internal static class MermaidSnapshots +{ + /// The diagram file name carries a hash of the rendered SVG, which changes with the renderer, not with the markup. + public static SnapshotOptions Options => new SnapshotOptions().Scrub(new Regex(@"index-[0-9a-f]{8}\.svg"), "index-{hash}.svg"); +} + public class MermaidFlowchartTests() : DirectiveTest(""" ```mermaid flowchart LR @@ -25,10 +35,20 @@ flowchart LR public void HasMermaidLanguage() => Block!.Language.Should().Be("mermaid"); [Test] - public void RendersMermaidContainer() => Html.Should().Contain("
    "); - - [Test] - public void RendersImgElement() => Html.Should().Contain(" + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + options: MermaidSnapshots.Options, + expected: """ +
    +
    +
    + Mermaid diagram +
    +
    +
    + """ + ); [Test] public void EmitsExternalSvgFile() => ReadMermaidSvgs().Should().NotBeEmpty(); @@ -61,10 +81,20 @@ participant B as Bob public void ParsesSequenceDiagram() => Block.Should().NotBeNull(); [Test] - public void RendersMermaidContainer() => Html.Should().Contain("
    "); - - [Test] - public void RendersImgElement() => Html.Should().Contain(" + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + options: MermaidSnapshots.Options, + expected: """ +
    +
    +
    + Mermaid diagram +
    +
    +
    + """ + ); [Test] public void SvgContainsParticipantLabels() @@ -93,10 +123,20 @@ public class MermaidStateDiagramTests() : DirectiveTest( public void ParsesStateDiagram() => Block.Should().NotBeNull(); [Test] - public void RendersMermaidContainer() => Html.Should().Contain("
    "); - - [Test] - public void RendersImgElement() => Html.Should().Contain(" + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + options: MermaidSnapshots.Options, + expected: """ +
    +
    +
    + Mermaid diagram +
    +
    +
    + """ + ); [Test] public void SvgContainsStateLabels() @@ -125,10 +165,20 @@ public class MermaidClassDiagramTests() : DirectiveTest( public void ParsesClassDiagram() => Block.Should().NotBeNull(); [Test] - public void RendersMermaidContainer() => Html.Should().Contain("
    "); - - [Test] - public void RendersImgElement() => Html.Should().Contain(" + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + options: MermaidSnapshots.Options, + expected: """ +
    +
    +
    + Mermaid diagram +
    +
    +
    + """ + ); [Test] public void SvgContainsClassLabels() @@ -156,10 +206,20 @@ public class MermaidErDiagramTests() : DirectiveTest( public void ParsesErDiagram() => Block.Should().NotBeNull(); [Test] - public void RendersMermaidContainer() => Html.Should().Contain("
    "); - - [Test] - public void RendersImgElement() => Html.Should().Contain(" + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + options: MermaidSnapshots.Options, + expected: """ +
    +
    +
    + Mermaid diagram +
    +
    +
    + """ + ); [Test] public void SvgContainsEntityLabels() @@ -192,7 +252,20 @@ class A elasticBlue public void EmitsSvgFile() => ReadMermaidSvgs().Should().NotBeEmpty(); [Test] - public void DoesNotFallBackToRawSource() => Html.Should().NotContain("
    ");
    +	public void RendersHtml() =>
    +		Html.Should().MatchInlineHtmlSnapshot(
    +			html: DocsHtml.Body(),
    +			options: MermaidSnapshots.Options,
    +			expected: """
    +			
    +
    +
    + Mermaid diagram +
    +
    +
    + """ + ); } // Allowlisted semantic classes render correctly with site palette colors baked into SVG. @@ -204,10 +277,20 @@ flowchart LR """) { [Test] - public void RendersMermaidContainer() => Html.Should().Contain("
    "); - - [Test] - public void RendersImgElement() => Html.Should().Contain(" + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + options: MermaidSnapshots.Options, + expected: """ +
    +
    +
    + Mermaid diagram +
    +
    +
    + """ + ); [Test] public void EmitsNoDiagnostics() => Collector.Diagnostics.Should().BeEmpty(); @@ -227,7 +310,20 @@ public class MermaidPieDataPaletteTests() : DirectiveTest(""" """) { [Test] - public void RendersImgElement() => Html.Should().Contain(" + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + options: MermaidSnapshots.Options, + expected: """ +
    +
    +
    + Mermaid diagram +
    +
    +
    + """ + ); [Test] public void EmitsNoDiagnostics() => Collector.Diagnostics.Should().BeEmpty(); diff --git a/tests/Elastic.Markdown.Tests/Directives/VectorSizingBlockTests.cs b/tests/Elastic.Markdown.Tests/Directives/VectorSizingBlockTests.cs index ad8bd22acc..f66599ebb7 100644 --- a/tests/Elastic.Markdown.Tests/Directives/VectorSizingBlockTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/VectorSizingBlockTests.cs @@ -3,7 +3,9 @@ // See the LICENSE file in the project root for more information using AwesomeAssertions; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.VectorSizing; +using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -20,5 +22,6 @@ public class VectorSizingBlockTests() : DirectiveTest(""" public void SetsCorrectDirectiveType() => Block!.Directive.Should().Be("vector-sizing-calculator"); [Test] - public void RendersCustomElement() => Html.Should().Contain(""); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot(html: DocsHtml.Body(), expected: ""); } From 5d055017d318f5117dc26cb72e4599016b5b461c Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 12:44:00 +0100 Subject: [PATCH 10/18] Directives tests: finish the csv, sub-pages, storybook and table HTML snapshots The remaining single-fragment Contain and NotContain tests of these classes became the class's whole-output snapshot, so the tests are stricter: every old fragment was checked to appear in (or, for NotContain, be absent from) the accepted snapshot before it was accepted. Co-Authored-By: Claude Sonnet 5.5 --- .../RendersHtml.verified.html | 6 ++ .../RendersHtml.verified.html | 20 ++++ .../Directives/CsvIncludeTests.cs | 28 +++++- .../Directives/ListSubPagesTests.cs | 16 ++- .../Directives/StorybookTests.cs | 2 +- .../Directives/TableDirectiveTests.cs | 99 +++++++++++++++++-- 6 files changed, 157 insertions(+), 14 deletions(-) create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/StorybookBodyTests/RendersHtml.verified.html create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/TableDirectiveDescriptionPresetTests/RendersHtml.verified.html diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/StorybookBodyTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/StorybookBodyTests/RendersHtml.verified.html new file mode 100644 index 0000000000..82811d5e22 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/StorybookBodyTests/RendersHtml.verified.html @@ -0,0 +1,6 @@ +
    + +
    +

    Supporting details for this story.

    +
    +
    diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/TableDirectiveDescriptionPresetTests/RendersHtml.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/TableDirectiveDescriptionPresetTests/RendersHtml.verified.html new file mode 100644 index 0000000000..91ac8c298a --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/TableDirectiveDescriptionPresetTests/RendersHtml.verified.html @@ -0,0 +1,20 @@ +
    + + + + + + + + + + + + + + + + + +
    TermDescription
    fooA thing
    +
    diff --git a/tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs b/tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs index f28b2245d4..a43c1d1cb3 100644 --- a/tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs @@ -163,7 +163,30 @@ protected override void AddToFileSystem(MockFileSystem fileSystem) => Search,[Text](https://www.google.com)")); [Test] - public void RendersMarkdownLinkAsLink() => Html.Should().Contain(">Text"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    + + + + + + + + + + + + + +
    NameLink
    Search + Text +
    +
    + """ + ); } public class CsvIncludeWithHtmlBreaksTests() : DirectiveTest(""" @@ -182,9 +205,6 @@ protected override void AddToFileSystem(MockFileSystem fileSystem) => ) ); - [Test] - public void RendersHtmlBreaksInCsvCells() => Html.Should().Contain(" Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } diff --git a/tests/Elastic.Markdown.Tests/Directives/ListSubPagesTests.cs b/tests/Elastic.Markdown.Tests/Directives/ListSubPagesTests.cs index 431e158975..ada114ce50 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ListSubPagesTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ListSubPagesTests.cs @@ -84,7 +84,21 @@ public void IncludesDescriptionWhenPresent() } [Test] - public void RendersDescriptionInOutput() => Html.Should().Contain("First page description"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ + + """ + ); } [InheritsTests] diff --git a/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs b/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs index c4df85a2f1..c93f5337a6 100644 --- a/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs @@ -242,7 +242,7 @@ public class StorybookBodyTests() : StorybookRegistryTest( ) { [Test] - public void RendersBodyContent() => Html.Should().Contain("Supporting details for this story."); + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] diff --git a/tests/Elastic.Markdown.Tests/Directives/TableDirectiveTests.cs b/tests/Elastic.Markdown.Tests/Directives/TableDirectiveTests.cs index b624adbbb4..f69ed7901e 100644 --- a/tests/Elastic.Markdown.Tests/Directives/TableDirectiveTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/TableDirectiveTests.cs @@ -99,7 +99,7 @@ public void MapsDescriptionTo4_8() } [Test] - public void RendersColgroup() => Html.Should().Contain("colgroup"); + public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); } [InheritsTests] @@ -119,7 +119,30 @@ public class TableDirectiveAutoPresetTests() : DirectiveTest Block!.ColumnWidths.Should().BeEmpty(); [Test] - public void DoesNotInjectColgroup() => Html.Should().NotContain("colgroup"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    + + + + + + + + + + + + + + + +
    abc
    123
    +
    + """ + ); } [InheritsTests] @@ -139,7 +162,28 @@ public class TableDirectiveMatrixTests() : DirectiveTest( public void ParsesMatrixOption() => Block!.Matrix.Should().BeTrue(); [Test] - public void RendersMatrixClass() => Html.Should().Contain("table-wrapper table-matrix"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    + + + + + + + + + + + + + +
    head ahead b
    ab
    +
    + """ + ); } [InheritsTests] @@ -154,7 +198,28 @@ public class TableDirectiveWithoutMatrixTests() : DirectiveTest Html.Should().NotContain("table-matrix"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    + + + + + + + + + + + + + +
    head ahead b
    ab
    +
    + """ + ); } [InheritsTests] @@ -198,9 +263,6 @@ public void RendersHtml() => """ ); - - [Test] - public void KeepsServerRenderedTableInsideHost() => Html.Should().Contain("
    Block!.Filterable.Should().BeFalse(); [Test] - public void DoesNotWrapTable() => Html.Should().NotContain("filterable-table"); + public void RendersHtml() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    + + + + + + + + + + + + + +
    head ahead b
    ab
    +
    + """ + ); } [InheritsTests] From 40750563a675be0147d1f44c5a7735639a95d213 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 12:45:30 +0100 Subject: [PATCH 11/18] Directives tests: snapshot the HTML of changelog, button and storybook tests that also assert on the block Tests that asserted on the parsed block and on fragments of the rendered HTML keep the block assertions and check the HTML against a snapshot, so they are stricter: every old fragment is in the accepted snapshot, and the NotContain fragments are absent. The button group's Contain(Primary).And.Contain(Secondary) test is covered by the group snapshot. Tests whose HTML expectation is derived from the block (TOC slugs, catalog titles and URLs, element order and counts) keep their assertions. Co-Authored-By: Claude Sonnet 5.5 --- ...DoesNotEmitHighlightsSection.verified.html | 46 ++++++++ .../Directives/ButtonTests.cs | 3 - .../Directives/ChangelogBasicTests.cs | 43 ++++++- .../ChangelogHighlightsOptionTests.cs | 4 +- .../ChangelogVersionVisibilityTests.cs | 108 +++++++++++++++--- .../Directives/StorybookTests.cs | 19 ++- 6 files changed, 195 insertions(+), 28 deletions(-) create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionTypeAllWithoutFlagTests/TypeAllAloneDoesNotEmitHighlightsSection.verified.html diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionTypeAllWithoutFlagTests/TypeAllAloneDoesNotEmitHighlightsSection.verified.html b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionTypeAllWithoutFlagTests/TypeAllAloneDoesNotEmitHighlightsSection.verified.html new file mode 100644 index 0000000000..241b76e6a9 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ChangelogHighlightsOptionTypeAllWithoutFlagTests/TypeAllAloneDoesNotEmitHighlightsSection.verified.html @@ -0,0 +1,46 @@ +
    +

    + 9.3.0 +

    +
    + +
      +
    • +

      Breaking API change. + #444444 +

      +

      + Impact: + Users must update.

      +

      + Action: + Follow guide.

      +
    • +
    + + +
    +

    + Fixes +

    +
    + diff --git a/tests/Elastic.Markdown.Tests/Directives/ButtonTests.cs b/tests/Elastic.Markdown.Tests/Directives/ButtonTests.cs index 1777b66367..980762c408 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ButtonTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ButtonTests.cs @@ -272,9 +272,6 @@ public void RendersHtml() =>
    """ ); - - [Test] - public void ContainsBothButtons() => Html.Should().Contain("Primary").And.Contain("Secondary"); } [InheritsTests] diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs index f144f003fb..862d4ccdf6 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs @@ -612,8 +612,26 @@ protected override IReleaseNotesResolver GetReleaseNotesResolver() => public void RendersOnlyMatchingVersion() { Block!.Found.Should().BeTrue(); - Html.Should().Contain("Selected version entry"); - Html.Should().NotContain("Filtered out entry"); + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.4.0 +

    +
    + +
      +
    • Selected version entry. + #999 +
    • +
    + """ + ); } } @@ -668,7 +686,26 @@ protected override IReleaseNotesResolver GetReleaseNotesResolver() => public void RendersInferredCdnBundleBody() { Block!.Found.Should().BeTrue(); - Html.Should().Contain("Inferred product from the repository"); + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 9.4.0 +

    +
    + +
      +
    • Inferred product from the repository. + #999 +
    • +
    + """ + ); } } diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogHighlightsOptionTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogHighlightsOptionTests.cs index 4a9a4c3761..9811aeec7e 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogHighlightsOptionTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogHighlightsOptionTests.cs @@ -150,9 +150,7 @@ public ChangelogHighlightsOptionTypeAllWithoutFlagTests() : base( [Test] public void TypeAllAloneDoesNotEmitHighlightsSection() { - Html.Should().Contain("Breaking changes"); - Html.Should().Contain("Highlighted feature"); - Html.Should().NotContain("id=\"elasticsearch-9.3.0-highlights\""); + Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); Block!.GeneratedTableOfContent.Should().NotContain(t => t.Heading == "Highlights"); } } diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogVersionVisibilityTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogVersionVisibilityTests.cs index 7d4bb5adc8..3c791eb6ae 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogVersionVisibilityTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogVersionVisibilityTests.cs @@ -78,11 +78,27 @@ public class ChangelogVisibilityOnProductionTests() : ChangelogVersionVisibility protected override ContentSource? GetContentSource() => ContentSource.Current; [Test] - public void HidesBundlesTargetingUnreleasedVersions() - { - Html.Should().Contain(ReleasedTitle); - Html.Should().NotContain(UnreleasedTitle, "8.1.0 is newer than the current release (8.0.0) and production publishes 'current'"); - } + public void HidesBundlesTargetingUnreleasedVersions() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 7.9.0 +

    +
    + +
      +
    • Released change. + #100 +
    • +
    + """ + ); [Test] public void EmitsHintForHiddenBundle() => @@ -95,11 +111,42 @@ public class ChangelogVisibilityOnStagingTests() : ChangelogVersionVisibilityTes protected override ContentSource? GetContentSource() => ContentSource.Next; [Test] - public void ShowsUnreleasedBundlesForPreReleaseReview() - { - Html.Should().Contain(ReleasedTitle); - Html.Should().Contain(UnreleasedTitle, "staging publishes 'next' so staged bundles are reviewable before release day"); - } + public void ShowsUnreleasedBundlesForPreReleaseReview() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 8.1.0 +

    +
    + +
      +
    • Unreleased staged change. + #200 +
    • +
    +
    +

    + 7.9.0 +

    +
    + +
      +
    • Released change. + #100 +
    • +
    + """ + ); } [InheritsTests] @@ -108,11 +155,42 @@ public class ChangelogVisibilityOnIsolatedBuildTests() : ChangelogVersionVisibil // No override: isolated/local builds have no content source and render everything. [Test] - public void ShowsAllBundles() - { - Html.Should().Contain(ReleasedTitle); - Html.Should().Contain(UnreleasedTitle); - } + public void ShowsAllBundles() => + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    +

    + 8.1.0 +

    +
    + +
      +
    • Unreleased staged change. + #200 +
    • +
    +
    +

    + 7.9.0 +

    +
    + +
      +
    • Released change. + #100 +
    • +
    + """ + ); } /// diff --git a/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs b/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs index c93f5337a6..53aef69e8a 100644 --- a/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs @@ -224,9 +224,13 @@ public class StorybookIframeTests() : StorybookRegistryTest(""" public void RendersIframeFallback() { Block!.HasInlineStory.Should().BeFalse(); - Html.Should().Contain(" + +
    + """ ); } } @@ -263,7 +267,14 @@ public void WarnsAndFallsBackToDefaultHeight() .Diagnostics .Should() .ContainSingle(d => d.Severity == Severity.Warning && d.Message.Contains(":height: must be a positive integer")); - Html.Should().Contain("height:400px"); + Html.Should().MatchInlineHtmlSnapshot( + html: DocsHtml.Body(), + expected: """ +
    + +
    + """ + ); } } From d97241ee2feadd38a6bf4cdaba3bb17a7801cf56 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 12:50:16 +0100 Subject: [PATCH 12/18] Directives tests: snapshot the changelog diagnostics The Collector assertions (empty, counts, severity and message-substring predicates) became a snapshot of the whole diagnostics text (error|warning|hint file:line message), so the tests are stricter: they now also pin the file and line and fail on any additional diagnostic. Every old message fragment and severity was checked to appear in the accepted text, and the empty-diagnostics tests are MatchInlineSnapshot(""). Tests that became identical were merged. Co-Authored-By: Claude Sonnet 5.5 --- .../Directives/ChangelogBasicTests.cs | 67 +++++++++++++------ .../Directives/ChangelogConfigTests.cs | 17 ++++- .../ChangelogDescriptionVisibilityTests.cs | 10 ++- .../Directives/ChangelogHideLinksTests.cs | 10 ++- .../ChangelogHighlightsOptionTests.cs | 9 ++- .../Directives/ChangelogMissingFileTests.cs | 15 ++--- .../Directives/ChangelogTypeFilterTests.cs | 9 ++- .../ChangelogVersionVisibilityTests.cs | 9 ++- 8 files changed, 104 insertions(+), 42 deletions(-) diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs index 862d4ccdf6..3dd4d97c98 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs @@ -389,7 +389,11 @@ public ChangelogVersionFilterNoMatchTests() : base( [Test] public void EmitsWarningForUnmatchedVersion() => - Collector.Diagnostics.Should().Contain(d => d.Message.Contains("No changelog bundle matches :version:")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot("warning {repo}/docs/index.md:3 No changelog bundle matches :version: '1.2.3'."); } [InheritsTests] @@ -475,11 +479,14 @@ public class ChangelogCdnInvalidProductTests() : DirectiveTest( public void DoesNotSourceFromLocalFolder() => Block!.BundlesFolderPath.Should().BeNull(); [Test] - public void EmitsErrorForInvalidProduct() - { - Collector.Diagnostics.Should().NotBeNullOrEmpty(); - Collector.Diagnostics.Should().Contain(d => d.Message.Contains("Invalid :cdn: product")); - } + public void EmitsErrorForInvalidProduct() => + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + "error {repo}/docs/index.md:3 Invalid :cdn: product 'invalid$product'. Product names must match [a-zA-Z0-9_-]+." + ); } /// @@ -730,7 +737,13 @@ public class ChangelogCdnInferredProductUnavailableTests() : DirectiveTest d.Message.Contains("could not be inferred")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + "error {repo}/docs/index.md:3 The :cdn: product could not be inferred from the repository; specify it explicitly, e.g. ':cdn: elasticsearch'." + ); } } @@ -754,8 +767,15 @@ public void EmitsErrorWhenProductIsNotDeclared() Block!.Found.Should().BeFalse(); Collector .Diagnostics + .ToSnapshotText() .Should() - .Contain(d => d.Message.Contains("not declared in docset.yml") && d.Message.Contains("release_notes")); + .MatchInlineSnapshot( + """ + error {repo}/docs/index.md:3 The :cdn: product 'not-declared' is not declared in docset.yml. Add it under 'release_notes:', for example: + release_notes: + - product: not-declared + """ + ); } } @@ -792,11 +812,12 @@ public class ChangelogNotFoundTests() : DirectiveTest( public void ReportsFolderNotFound() => Block!.Found.Should().BeFalse(); [Test] - public void EmitsErrorForMissingFolder() - { - Collector.Diagnostics.Should().NotBeNullOrEmpty(); - Collector.Diagnostics.Should().OnlyContain(d => d.Message.Contains("does not exist")); - } + public void EmitsErrorForMissingFolder() => + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot("error {repo}/docs/index.md:3 Changelog bundles folder `missing-bundles` does not exist."); } [InheritsTests] @@ -812,8 +833,11 @@ public class ChangelogDefaultPathMissingTests() : DirectiveTest( public void EmitsErrorForMissingDefaultFolder() { // No bundles folder created, so it should emit an error - Collector.Diagnostics.Should().NotBeNullOrEmpty(); - Collector.Diagnostics.Should().OnlyContain(d => d.Message.Contains("does not exist")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot("error {repo}/docs/index.md:3 Changelog bundles folder `changelog/bundles` does not exist."); } } @@ -949,11 +973,12 @@ public ChangelogEmptyFolderTests() : base( public void ReportsFolderEmpty() => Block!.Found.Should().BeFalse(); [Test] - public void EmitsErrorForEmptyFolder() - { - Collector.Diagnostics.Should().NotBeNullOrEmpty(); - Collector.Diagnostics.Should().OnlyContain(d => d.Message.Contains("contains no YAML files")); - } + public void EmitsErrorForEmptyFolder() => + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot("error {repo}/docs/index.md:3 Changelog bundles folder `changelog/bundles` contains no YAML files."); } [InheritsTests] @@ -1468,7 +1493,7 @@ public ChangelogDoubleBraceEscapeTests() : base( ); [Test] - public void RendersWithoutErrors() => Collector.Errors.Should().Be(0); + public void RendersWithoutErrors() => Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); [Test] public void RendersHtml() => diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogConfigTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogConfigTests.cs index b9ea5d3157..e4b4c9f51a 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogConfigTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogConfigTests.cs @@ -6,6 +6,7 @@ using AwesomeAssertions; using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots; using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -268,7 +269,7 @@ public void RendersHtml() => ); [Test] - public void NoErrorsEmittedForMissingConfig() => Collector.Diagnostics.Should().NotContain(d => d.Message.Contains("changelog.yml")); + public void NoErrorsEmittedForMissingConfig() => Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } [InheritsTests] @@ -307,7 +308,11 @@ public ChangelogConfigExplicitPathNotFoundTests() : base( [Test] public void EmitsWarningForMissingExplicitConfig() => - Collector.Diagnostics.Should().Contain(d => d.Message.Contains("nonexistent/config.yml") && d.Message.Contains("not found")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot("warning {repo}/docs/index.md:3 Specified changelog config path 'nonexistent/config.yml' not found."); [Test] public void RendersHtml() => @@ -825,7 +830,13 @@ protected override void AddToFileSystem(MockFileSystem fileSystem) [Test] public void EmitsDeprecationWarningForProductOption() => - Collector.Diagnostics.Should().Contain(d => d.Message.Contains(":product:") && d.Message.Contains("deprecated")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + "warning {repo}/docs/index.md:3 The :product: option is deprecated and has no effect. The directive does not apply rules.publish. Move type/area filtering to rules.bundle so it applies at bundle time." + ); [Test] public void RendersHtml() => Html.Should().MatchHtmlSnapshot(DocsHtml.Body()); diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogDescriptionVisibilityTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogDescriptionVisibilityTests.cs index fcb2eca043..c746cd2444 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogDescriptionVisibilityTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogDescriptionVisibilityTests.cs @@ -7,6 +7,7 @@ using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; using Markdig.Syntax; +using Nullean.Snapshots; using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -361,7 +362,14 @@ protected override void AddToFileSystem(MockFileSystem fileSystem) => public void FallsBackToAuto() => Block!.DescriptionVisibility.Should().Be(ChangelogDescriptionVisibility.Auto); [Test] - public void EmitsWarning() => Collector.Warnings.Should().BeGreaterThan(0); + public void EmitsWarning() => + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + "warning {repo}/docs/index.md:3 Invalid :description-visibility: value 'nonsense-value'. Valid values are: auto, keep-descriptions, keep-highlight-descriptions, hide-descriptions. Using auto." + ); [Test] public void RendersHtml() => diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogHideLinksTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogHideLinksTests.cs index d340190b8b..31c2ed5153 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogHideLinksTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogHideLinksTests.cs @@ -8,6 +8,7 @@ using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; using Markdig.Syntax; +using Nullean.Snapshots; using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -783,7 +784,14 @@ public ChangelogLinkVisibilityInvalidTests() : base( public void LinkVisibilityFallsBackToAuto() => Block!.LinkVisibility.Should().Be(ChangelogLinkVisibility.Auto); [Test] - public void EmitsWarning() => Collector.Warnings.Should().BeGreaterThan(0); + public void EmitsWarning() => + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + "warning {repo}/docs/index.md:3 Invalid :link-visibility: value 'banana'. Valid values are: auto, keep-links, hide-links. Using auto." + ); } /// diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogHighlightsOptionTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogHighlightsOptionTests.cs index 9811aeec7e..501fc2a43e 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogHighlightsOptionTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogHighlightsOptionTests.cs @@ -6,6 +6,7 @@ using AwesomeAssertions; using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots; using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -178,12 +179,10 @@ public ChangelogHighlightsLegacyTypeHighlightTests() : base( public void EmitsWarningPointingToHighlightsOption() => Collector .Diagnostics + .ToSnapshotText() .Should() - .Contain( - d => d.Message.Contains("Invalid :type: value 'highlight'", StringComparison.Ordinal) && d.Message.Contains( - ":highlights:", - StringComparison.Ordinal - ) + .MatchInlineSnapshot( + "warning {repo}/docs/index.md:3 Invalid :type: value 'highlight'. Highlights are controlled with :highlights: (not :type:). Using default behavior." ); [Test] diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogMissingFileTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogMissingFileTests.cs index a5f2619d96..7af2a3d831 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogMissingFileTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogMissingFileTests.cs @@ -5,7 +5,9 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots; namespace Elastic.Markdown.Tests.Directives; @@ -65,17 +67,12 @@ public ChangelogEntryWithoutInlineContentTests() : base( public void EmitsErrorNamingBundleAndEntry() => Collector .Diagnostics + .ToSnapshotText() .Should() - .ContainSingle( - d => d.Severity == Severity.Error && d.Message.Contains("9.3.0.yaml") && d.Message.Contains( - "1234-referenced-entry.yaml" - ) && d.Message.Contains("no inline content") + .MatchInlineSnapshot( + "error {repo}/docs/index.md:3 Bundle '9.3.0.yaml' entry '1234-referenced-entry.yaml' has no inline content (title and type are required); bundles must inline their entries. Skipping." ); - [Test] - public void ErrorIsNotAWarning() => - Collector.Diagnostics.Should().NotContain(d => d.Severity == Severity.Warning && d.Message.Contains("1234-referenced-entry.yaml")); - [Test] public void NeverLoadsTheReferencedFile() => Block!.LoadedBundles.Should().ContainSingle(b => b.Entries.Count == 0); } @@ -116,7 +113,7 @@ public ChangelogInlineEntriesNoErrorTests() : base( ); [Test] - public void HasNoDiagnostics() => Collector.Diagnostics.Should().BeEmpty(); + public void HasNoDiagnostics() => Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); [Test] public void LoadsEntries() => Block!.LoadedBundles.Should().ContainSingle(b => b.Entries.Count == 1); diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogTypeFilterTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogTypeFilterTests.cs index bc85e7b80a..2fac1f38bb 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogTypeFilterTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogTypeFilterTests.cs @@ -448,7 +448,14 @@ public ChangelogTypeFilterInvalidTests() : base( public void FallsBackToDefaultBehavior() => Block!.TypeFilter.Should().Be(ChangelogTypeFilter.Default); [Test] - public void EmitsWarningForInvalidValue() => Collector.Diagnostics.Should().Contain(d => d.Message.Contains("Invalid :type: value")); + public void EmitsWarningForInvalidValue() => + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + "warning {repo}/docs/index.md:3 Invalid :type: value 'invalid-value'. Valid values are: all, breaking-change, deprecation, known-issue. Using default behavior." + ); [Test] public void RendersHtml() => diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogVersionVisibilityTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogVersionVisibilityTests.cs index 3c791eb6ae..a3185b08c4 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogVersionVisibilityTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogVersionVisibilityTests.cs @@ -8,6 +8,7 @@ using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Changelog; +using Nullean.Snapshots; using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -102,7 +103,13 @@ public void HidesBundlesTargetingUnreleasedVersions() => [Test] public void EmitsHintForHiddenBundle() => - Collector.Diagnostics.Should().Contain(d => d.Severity == Severity.Hint && d.Message.Contains("elasticsearch 8.1.0")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + "hint {repo}/docs/index.md:3 Hiding changelog bundle 'elasticsearch 8.1.0': it targets a version newer than the current release (8.0.0) and this build publishes the 'current' content source." + ); } [InheritsTests] From 0167c8c74ab0a41545babb7ddbabf8dbcb34eed7 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 12:50:18 +0100 Subject: [PATCH 13/18] Directives tests: snapshot the diagnostics of the other directives The Collector assertions (empty, counts, severity and message-substring predicates) became a snapshot of the whole diagnostics text (error|warning|hint file:line message), so the tests are stricter: they now also pin the file and line and fail on any additional diagnostic. Every old message fragment and severity was checked to appear in the accepted text, and the empty-diagnostics tests are MatchInlineSnapshot(""). Tests that became identical were merged. The unsupported directive tests differ per derived class, so they use a file snapshot each. Co-Authored-By: Claude Sonnet 5.5 --- .../EmitsUnsupportedWarnings.verified.txt | 1 + .../EmitsUnsupportedWarnings.verified.txt | 1 + .../EmitsUnsupportedWarnings.verified.txt | 1 + .../EmitsUnsupportedWarnings.verified.txt | 1 + .../EmitsUnsupportedWarnings.verified.txt | 1 + .../EmitsUnsupportedWarnings.verified.txt | 1 + .../EmitsUnsupportedWarnings.verified.txt | 1 + .../EmitsUnsupportedWarnings.verified.txt | 1 + .../EmitsUnsupportedWarnings.verified.txt | 1 + .../Directives/AgentSkillTests.cs | 15 ++- .../ApplicabilitySwitchMalformedYamlTests.cs | 106 +++++++++++++++++- .../Directives/ButtonTests.cs | 86 ++++++++++++-- .../Directives/CsvIncludeTests.cs | 23 ++-- .../Directives/ImageCarouselTests.cs | 18 ++- .../Directives/ImageTests.cs | 14 ++- .../Directives/MathBlockTests.cs | 12 +- .../Directives/MermaidTests.cs | 13 ++- .../Directives/PageCardBlockTests.cs | 18 ++- .../Directives/RelatedLearningTests.cs | 13 ++- .../Directives/StorybookTests.cs | 43 ++++++- .../Directives/TableDirectiveTests.cs | 43 ++++++- .../Directives/UnsupportedTests.cs | 12 +- .../Directives/VersionTests.cs | 8 +- 23 files changed, 356 insertions(+), 77 deletions(-) create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/BibliographyDirectiveTests/EmitsUnsupportedWarnings.verified.txt create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/BlockQuoteDirectiveTests/EmitsUnsupportedWarnings.verified.txt create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/CsvTableDirectiveTests/EmitsUnsupportedWarnings.verified.txt create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/ExerciseDirectiveTest/EmitsUnsupportedWarnings.verified.txt create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/FrameDirectiveTests/EmitsUnsupportedWarnings.verified.txt create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/MystDirectiveDirectiveTests/EmitsUnsupportedWarnings.verified.txt create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/SolutionDirectiveTests/EmitsUnsupportedWarnings.verified.txt create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/TocTreeDirectiveTests/EmitsUnsupportedWarnings.verified.txt create mode 100644 tests/Elastic.Markdown.Tests/.snapshots/Directives/TopicDirectiveTests/EmitsUnsupportedWarnings.verified.txt diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/BibliographyDirectiveTests/EmitsUnsupportedWarnings.verified.txt b/tests/Elastic.Markdown.Tests/.snapshots/Directives/BibliographyDirectiveTests/EmitsUnsupportedWarnings.verified.txt new file mode 100644 index 0000000000..e3819442b5 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/BibliographyDirectiveTests/EmitsUnsupportedWarnings.verified.txt @@ -0,0 +1 @@ +warning {repo}/docs/index.md:1 Directive block 'bibliography' is unsupported. See https://github.com/elastic/docs-builder/issues/5 for more information. diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/BlockQuoteDirectiveTests/EmitsUnsupportedWarnings.verified.txt b/tests/Elastic.Markdown.Tests/.snapshots/Directives/BlockQuoteDirectiveTests/EmitsUnsupportedWarnings.verified.txt new file mode 100644 index 0000000000..c49b50927b --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/BlockQuoteDirectiveTests/EmitsUnsupportedWarnings.verified.txt @@ -0,0 +1 @@ +warning {repo}/docs/index.md:1 Directive block 'blockquote' is unsupported. See https://github.com/elastic/docs-builder/issues/6 for more information. diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/CsvTableDirectiveTests/EmitsUnsupportedWarnings.verified.txt b/tests/Elastic.Markdown.Tests/.snapshots/Directives/CsvTableDirectiveTests/EmitsUnsupportedWarnings.verified.txt new file mode 100644 index 0000000000..e3f5ac361f --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/CsvTableDirectiveTests/EmitsUnsupportedWarnings.verified.txt @@ -0,0 +1 @@ +warning {repo}/docs/index.md:1 Directive block 'csv-table' is unsupported. See https://github.com/elastic/docs-builder/issues/33 for more information. diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/ExerciseDirectiveTest/EmitsUnsupportedWarnings.verified.txt b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ExerciseDirectiveTest/EmitsUnsupportedWarnings.verified.txt new file mode 100644 index 0000000000..e0fd299fb6 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/ExerciseDirectiveTest/EmitsUnsupportedWarnings.verified.txt @@ -0,0 +1 @@ +warning {repo}/docs/index.md:1 Directive block 'exercise' is unsupported. See https://github.com/elastic/docs-builder/issues/30 for more information. diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/FrameDirectiveTests/EmitsUnsupportedWarnings.verified.txt b/tests/Elastic.Markdown.Tests/.snapshots/Directives/FrameDirectiveTests/EmitsUnsupportedWarnings.verified.txt new file mode 100644 index 0000000000..5d2f16166c --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/FrameDirectiveTests/EmitsUnsupportedWarnings.verified.txt @@ -0,0 +1 @@ +warning {repo}/docs/index.md:1 Directive block 'iframe' is unsupported. See https://github.com/elastic/docs-builder/issues/14 for more information. diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/MystDirectiveDirectiveTests/EmitsUnsupportedWarnings.verified.txt b/tests/Elastic.Markdown.Tests/.snapshots/Directives/MystDirectiveDirectiveTests/EmitsUnsupportedWarnings.verified.txt new file mode 100644 index 0000000000..3ce73c34dc --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/MystDirectiveDirectiveTests/EmitsUnsupportedWarnings.verified.txt @@ -0,0 +1 @@ +warning {repo}/docs/index.md:1 Directive block 'myst' is unsupported. See https://github.com/elastic/docs-builder/issues/22 for more information. diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/SolutionDirectiveTests/EmitsUnsupportedWarnings.verified.txt b/tests/Elastic.Markdown.Tests/.snapshots/Directives/SolutionDirectiveTests/EmitsUnsupportedWarnings.verified.txt new file mode 100644 index 0000000000..f9d85a108d --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/SolutionDirectiveTests/EmitsUnsupportedWarnings.verified.txt @@ -0,0 +1 @@ +warning {repo}/docs/index.md:1 Directive block 'solution' is unsupported. See https://github.com/elastic/docs-builder/issues/31 for more information. diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/TocTreeDirectiveTests/EmitsUnsupportedWarnings.verified.txt b/tests/Elastic.Markdown.Tests/.snapshots/Directives/TocTreeDirectiveTests/EmitsUnsupportedWarnings.verified.txt new file mode 100644 index 0000000000..f9d85a108d --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/TocTreeDirectiveTests/EmitsUnsupportedWarnings.verified.txt @@ -0,0 +1 @@ +warning {repo}/docs/index.md:1 Directive block 'solution' is unsupported. See https://github.com/elastic/docs-builder/issues/31 for more information. diff --git a/tests/Elastic.Markdown.Tests/.snapshots/Directives/TopicDirectiveTests/EmitsUnsupportedWarnings.verified.txt b/tests/Elastic.Markdown.Tests/.snapshots/Directives/TopicDirectiveTests/EmitsUnsupportedWarnings.verified.txt new file mode 100644 index 0000000000..d3f15bfc78 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/.snapshots/Directives/TopicDirectiveTests/EmitsUnsupportedWarnings.verified.txt @@ -0,0 +1 @@ +warning {repo}/docs/index.md:1 Directive block 'topic' is unsupported. See https://github.com/elastic/docs-builder/issues/24 for more information. diff --git a/tests/Elastic.Markdown.Tests/Directives/AgentSkillTests.cs b/tests/Elastic.Markdown.Tests/Directives/AgentSkillTests.cs index fede4039c1..8842e5c635 100644 --- a/tests/Elastic.Markdown.Tests/Directives/AgentSkillTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/AgentSkillTests.cs @@ -5,6 +5,7 @@ using AwesomeAssertions; using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.AgentSkill; +using Nullean.Snapshots; using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -62,7 +63,12 @@ A regular paragraph. """) { [Test] - public void EmitsError() => Collector.Diagnostics.Should().Contain(d => d.Message.Contains("requires a :url: property")); + public void EmitsError() => + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot("error {repo}/docs/index.md:3 agent-skill directive requires a :url: property"); } [InheritsTests] @@ -76,7 +82,12 @@ A regular paragraph. ) { [Test] - public void EmitsError() => Collector.Diagnostics.Should().Contain(d => d.Message.Contains("must be an absolute URL")); + public void EmitsError() => + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot("error {repo}/docs/index.md:3 agent-skill :url: must be an absolute URL, got '/relative/path'"); } [InheritsTests] diff --git a/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchMalformedYamlTests.cs b/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchMalformedYamlTests.cs index b1fb4feeae..f29b947a7a 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchMalformedYamlTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchMalformedYamlTests.cs @@ -4,7 +4,9 @@ using AwesomeAssertions; using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.AppliesSwitch; +using Nullean.Snapshots; namespace Elastic.Markdown.Tests.Directives; @@ -28,8 +30,58 @@ This item has an applies_to definition that is not valid YAML. public void ReportsTheDefinitionAsAnError() => Collector .Diagnostics + .ToSnapshotText() .Should() - .Contain(d => d.Severity == Severity.Error && d.Message.Contains("Unable to parse applies_to definition: stack: [ga")); + .MatchInlineSnapshot( + """ + error {repo}/docs/index.md:5 Unable to parse applies_to definition: stack: [ga + SharpYaml.SemanticErrorException: (Lin: 1, Col: 0, Chr: 10) - (Lin: 1, Col: 0, Chr: 10): While parsing a flow sequence, did not find expected ',' or ']'. + at SharpYaml.Parser`1.ParseFlowSequenceEntry(Boolean isFirst) + at SharpYaml.Parser`1.StateMachine() + at SharpYaml.Parser`1.MoveNext() + at SharpYaml.Serialization.YamlReader.YamlReaderState.ReadCore() + at SharpYaml.Serialization.YamlReader.YamlReaderState.Read() + at SharpYaml.Serialization.YamlReader.Read() + at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) + at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) + at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) + at Elastic.Documentation.AppliesTo.ApplicableToYamlConverter.ReadUntyped(YamlReader reader) in {repo}/src/Elastic.Documentation.Configuration/Converters/ApplicableToYamlConverter.cs:line 41 + at Elastic.Documentation.AppliesTo.ApplicableToYamlConverter.Read(YamlReader reader) in {repo}/src/Elastic.Documentation.Configuration/Converters/ApplicableToYamlConverter.cs:line 94 + at SharpYaml.Serialization.YamlConverter`1.Read(YamlReader reader, Type typeToConvert) + at Elastic.Markdown.Myst.DocsBuilderYamlStaticContext.ReadValue4(YamlReader reader, RuntimeCustomConverterCache runtimeConverters) in {repo}/.artifacts/obj/Elastic.Markdown/release/SharpYaml.SourceGenerator/SharpYaml.SourceGeneration.YamlSerializerContextGenerator/DocsBuilderYamlStaticContext.g.cs:line 2768 + at Elastic.Markdown.Myst.DocsBuilderYamlStaticContext.GeneratedTypeInfo4.Read(YamlReader reader) in {repo}/.artifacts/obj/Elastic.Markdown/release/SharpYaml.SourceGenerator/SharpYaml.SourceGeneration.YamlSerializerContextGenerator/DocsBuilderYamlStaticContext.g.cs:line 485 + at SharpYaml.YamlTypeInfo`1.ReadAsObject(YamlReader reader) + at SharpYaml.YamlSerializer.DeserializeCore(YamlTypeInfo typeInfo, String yaml) + at SharpYaml.YamlSerializer.Deserialize(String yaml, Type returnType, YamlSerializerOptions options) + at SharpYaml.YamlSerializer.Deserialize[T](String yaml, YamlSerializerOptions options) + at Elastic.Markdown.Myst.YamlSerialization.Deserialize[T](String yaml, ProductsConfiguration products, Func`2 configure) in {repo}/src/Elastic.Markdown/Myst/YamlSerialization.cs:line 59 + at Elastic.Markdown.Myst.YamlSerialization.Deserialize[T](String yaml, ProductsConfiguration products) in {repo}/src/Elastic.Markdown/Myst/YamlSerialization.cs:line 21 + at Elastic.Markdown.Myst.Directives.AppliesSwitch.AppliesItemBlock.ParseApplicableTo(String yaml) in {repo}/src/Elastic.Markdown/Myst/Directives/AppliesSwitch/AppliesSwitchBlock.cs:line 78 + error {repo}/docs/index.md:5 Unable to parse applies_to definition: stack: [ga + SharpYaml.SemanticErrorException: (Lin: 1, Col: 0, Chr: 10) - (Lin: 1, Col: 0, Chr: 10): While parsing a flow sequence, did not find expected ',' or ']'. + at SharpYaml.Parser`1.ParseFlowSequenceEntry(Boolean isFirst) + at SharpYaml.Parser`1.StateMachine() + at SharpYaml.Parser`1.MoveNext() + at SharpYaml.Serialization.YamlReader.YamlReaderState.ReadCore() + at SharpYaml.Serialization.YamlReader.YamlReaderState.Read() + at SharpYaml.Serialization.YamlReader.Read() + at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) + at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) + at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) + at Elastic.Documentation.AppliesTo.ApplicableToYamlConverter.ReadUntyped(YamlReader reader) in {repo}/src/Elastic.Documentation.Configuration/Converters/ApplicableToYamlConverter.cs:line 41 + at Elastic.Documentation.AppliesTo.ApplicableToYamlConverter.Read(YamlReader reader) in {repo}/src/Elastic.Documentation.Configuration/Converters/ApplicableToYamlConverter.cs:line 94 + at SharpYaml.Serialization.YamlConverter`1.Read(YamlReader reader, Type typeToConvert) + at Elastic.Markdown.Myst.DocsBuilderYamlStaticContext.ReadValue4(YamlReader reader, RuntimeCustomConverterCache runtimeConverters) in {repo}/.artifacts/obj/Elastic.Markdown/release/SharpYaml.SourceGenerator/SharpYaml.SourceGeneration.YamlSerializerContextGenerator/DocsBuilderYamlStaticContext.g.cs:line 2768 + at Elastic.Markdown.Myst.DocsBuilderYamlStaticContext.GeneratedTypeInfo4.Read(YamlReader reader) in {repo}/.artifacts/obj/Elastic.Markdown/release/SharpYaml.SourceGenerator/SharpYaml.SourceGeneration.YamlSerializerContextGenerator/DocsBuilderYamlStaticContext.g.cs:line 485 + at SharpYaml.YamlTypeInfo`1.ReadAsObject(YamlReader reader) + at SharpYaml.YamlSerializer.DeserializeCore(YamlTypeInfo typeInfo, String yaml) + at SharpYaml.YamlSerializer.Deserialize(String yaml, Type returnType, YamlSerializerOptions options) + at SharpYaml.YamlSerializer.Deserialize[T](String yaml, YamlSerializerOptions options) + at Elastic.Markdown.Myst.YamlSerialization.Deserialize[T](String yaml, ProductsConfiguration products, Func`2 configure) in {repo}/src/Elastic.Markdown/Myst/YamlSerialization.cs:line 59 + at Elastic.Markdown.Myst.YamlSerialization.Deserialize[T](String yaml, ProductsConfiguration products) in {repo}/src/Elastic.Markdown/Myst/YamlSerialization.cs:line 21 + at Elastic.Markdown.Myst.Directives.DirectiveHtmlRenderer.ParseApplicableTo(String yaml, DirectiveBlock block) in {repo}/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs:line 648 + """ + ); [Test] public void KeepsTheExplicitSyncKey() => Block!.OfType().Single().SyncKey.Should().Be("explicit-key"); @@ -54,8 +106,58 @@ This item has an applies_to definition that is not valid YAML. public void ReportsTheDefinitionAsAnError() => Collector .Diagnostics + .ToSnapshotText() .Should() - .Contain(d => d.Severity == Severity.Error && d.Message.Contains("Unable to parse applies_to definition: stack: [ga")); + .MatchInlineSnapshot( + """ + error {repo}/docs/index.md:5 Unable to parse applies_to definition: stack: [ga + SharpYaml.SemanticErrorException: (Lin: 1, Col: 0, Chr: 10) - (Lin: 1, Col: 0, Chr: 10): While parsing a flow sequence, did not find expected ',' or ']'. + at SharpYaml.Parser`1.ParseFlowSequenceEntry(Boolean isFirst) + at SharpYaml.Parser`1.StateMachine() + at SharpYaml.Parser`1.MoveNext() + at SharpYaml.Serialization.YamlReader.YamlReaderState.ReadCore() + at SharpYaml.Serialization.YamlReader.YamlReaderState.Read() + at SharpYaml.Serialization.YamlReader.Read() + at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) + at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) + at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) + at Elastic.Documentation.AppliesTo.ApplicableToYamlConverter.ReadUntyped(YamlReader reader) in {repo}/src/Elastic.Documentation.Configuration/Converters/ApplicableToYamlConverter.cs:line 41 + at Elastic.Documentation.AppliesTo.ApplicableToYamlConverter.Read(YamlReader reader) in {repo}/src/Elastic.Documentation.Configuration/Converters/ApplicableToYamlConverter.cs:line 94 + at SharpYaml.Serialization.YamlConverter`1.Read(YamlReader reader, Type typeToConvert) + at Elastic.Markdown.Myst.DocsBuilderYamlStaticContext.ReadValue4(YamlReader reader, RuntimeCustomConverterCache runtimeConverters) in {repo}/.artifacts/obj/Elastic.Markdown/release/SharpYaml.SourceGenerator/SharpYaml.SourceGeneration.YamlSerializerContextGenerator/DocsBuilderYamlStaticContext.g.cs:line 2768 + at Elastic.Markdown.Myst.DocsBuilderYamlStaticContext.GeneratedTypeInfo4.Read(YamlReader reader) in {repo}/.artifacts/obj/Elastic.Markdown/release/SharpYaml.SourceGenerator/SharpYaml.SourceGeneration.YamlSerializerContextGenerator/DocsBuilderYamlStaticContext.g.cs:line 485 + at SharpYaml.YamlTypeInfo`1.ReadAsObject(YamlReader reader) + at SharpYaml.YamlSerializer.DeserializeCore(YamlTypeInfo typeInfo, String yaml) + at SharpYaml.YamlSerializer.Deserialize(String yaml, Type returnType, YamlSerializerOptions options) + at SharpYaml.YamlSerializer.Deserialize[T](String yaml, YamlSerializerOptions options) + at Elastic.Markdown.Myst.YamlSerialization.Deserialize[T](String yaml, ProductsConfiguration products, Func`2 configure) in {repo}/src/Elastic.Markdown/Myst/YamlSerialization.cs:line 59 + at Elastic.Markdown.Myst.YamlSerialization.Deserialize[T](String yaml, ProductsConfiguration products) in {repo}/src/Elastic.Markdown/Myst/YamlSerialization.cs:line 21 + at Elastic.Markdown.Myst.Directives.AppliesSwitch.AppliesItemBlock.ParseApplicableTo(String yaml) in {repo}/src/Elastic.Markdown/Myst/Directives/AppliesSwitch/AppliesSwitchBlock.cs:line 78 + error {repo}/docs/index.md:5 Unable to parse applies_to definition: stack: [ga + SharpYaml.SemanticErrorException: (Lin: 1, Col: 0, Chr: 10) - (Lin: 1, Col: 0, Chr: 10): While parsing a flow sequence, did not find expected ',' or ']'. + at SharpYaml.Parser`1.ParseFlowSequenceEntry(Boolean isFirst) + at SharpYaml.Parser`1.StateMachine() + at SharpYaml.Parser`1.MoveNext() + at SharpYaml.Serialization.YamlReader.YamlReaderState.ReadCore() + at SharpYaml.Serialization.YamlReader.YamlReaderState.Read() + at SharpYaml.Serialization.YamlReader.Read() + at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) + at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) + at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) + at Elastic.Documentation.AppliesTo.ApplicableToYamlConverter.ReadUntyped(YamlReader reader) in {repo}/src/Elastic.Documentation.Configuration/Converters/ApplicableToYamlConverter.cs:line 41 + at Elastic.Documentation.AppliesTo.ApplicableToYamlConverter.Read(YamlReader reader) in {repo}/src/Elastic.Documentation.Configuration/Converters/ApplicableToYamlConverter.cs:line 94 + at SharpYaml.Serialization.YamlConverter`1.Read(YamlReader reader, Type typeToConvert) + at Elastic.Markdown.Myst.DocsBuilderYamlStaticContext.ReadValue4(YamlReader reader, RuntimeCustomConverterCache runtimeConverters) in {repo}/.artifacts/obj/Elastic.Markdown/release/SharpYaml.SourceGenerator/SharpYaml.SourceGeneration.YamlSerializerContextGenerator/DocsBuilderYamlStaticContext.g.cs:line 2768 + at Elastic.Markdown.Myst.DocsBuilderYamlStaticContext.GeneratedTypeInfo4.Read(YamlReader reader) in {repo}/.artifacts/obj/Elastic.Markdown/release/SharpYaml.SourceGenerator/SharpYaml.SourceGeneration.YamlSerializerContextGenerator/DocsBuilderYamlStaticContext.g.cs:line 485 + at SharpYaml.YamlTypeInfo`1.ReadAsObject(YamlReader reader) + at SharpYaml.YamlSerializer.DeserializeCore(YamlTypeInfo typeInfo, String yaml) + at SharpYaml.YamlSerializer.Deserialize(String yaml, Type returnType, YamlSerializerOptions options) + at SharpYaml.YamlSerializer.Deserialize[T](String yaml, YamlSerializerOptions options) + at Elastic.Markdown.Myst.YamlSerialization.Deserialize[T](String yaml, ProductsConfiguration products, Func`2 configure) in {repo}/src/Elastic.Markdown/Myst/YamlSerialization.cs:line 59 + at Elastic.Markdown.Myst.YamlSerialization.Deserialize[T](String yaml, ProductsConfiguration products) in {repo}/src/Elastic.Markdown/Myst/YamlSerialization.cs:line 21 + at Elastic.Markdown.Myst.Directives.DirectiveHtmlRenderer.ParseApplicableTo(String yaml, DirectiveBlock block) in {repo}/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs:line 648 + """ + ); [Test] public void FallsBackToAKeyDerivedFromTheDefinition() => diff --git a/tests/Elastic.Markdown.Tests/Directives/ButtonTests.cs b/tests/Elastic.Markdown.Tests/Directives/ButtonTests.cs index 980762c408..577003f83e 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ButtonTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ButtonTests.cs @@ -5,6 +5,7 @@ using AwesomeAssertions; using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Button; +using Nullean.Snapshots; using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -91,7 +92,7 @@ public void RendersHtml() => ); [Test] - public void EmitsNoErrors() => Collector.Diagnostics.Should().BeEmpty(); + public void EmitsNoErrors() => Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } [InheritsTests] @@ -215,7 +216,7 @@ public void RendersHtml() => ); [Test] - public void EmitsNoErrors() => Collector.Diagnostics.Should().BeEmpty(); + public void EmitsNoErrors() => Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } [InheritsTests] @@ -228,7 +229,16 @@ [Invalid Type](/test) { [Test] public void EmitsWarningForInvalidType() => - Collector.Diagnostics.Should().ContainSingle(d => d.Message.Contains("Invalid button type")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + """ + warning {repo}/docs/index.md:3 Invalid button type 'invalid'. Valid types are: primary, secondary, neutral. Defaulting to 'primary'. + error {repo}/docs/index.md:5 `/test` does not exist. If it was recently removed add a redirect. resolved to `{repo}/docs/test` + """ + ); [Test] public void FallsBackToPrimary() => Block!.Type.Should().Be("primary"); @@ -383,7 +393,7 @@ public void RendersHtml() => ); [Test] - public void EmitsNoErrors() => Collector.Diagnostics.Should().BeEmpty(); + public void EmitsNoErrors() => Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } [InheritsTests] @@ -412,7 +422,7 @@ public void RendersHtml() => ); [Test] - public void EmitsNoErrors() => Collector.Diagnostics.Should().BeEmpty(); + public void EmitsNoErrors() => Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } [InheritsTests] @@ -441,7 +451,7 @@ public void RendersHtml() => ); [Test] - public void EmitsNoErrors() => Collector.Diagnostics.Should().BeEmpty(); + public void EmitsNoErrors() => Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } [InheritsTests] @@ -451,7 +461,21 @@ public class ButtonEmptyTests() : DirectiveTest(""" """) { [Test] - public void EmitsErrorForEmptyContent() => Collector.Diagnostics.Should().ContainSingle(d => d.Message.Contains("requires a link")); + public void EmitsErrorForEmptyContent() => + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + """ + error {repo}/docs/index.md:3 Button directive requires a link. Use: :::{button} + [text](url) + ::: + Or: :::{button} + [text][ref] + ::: + """ + ); } [InheritsTests] @@ -463,7 +487,20 @@ Just some text without a link { [Test] public void EmitsErrorForPlainText() => - Collector.Diagnostics.Should().ContainSingle(d => d.Message.Contains("must contain only a single Markdown link")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + """ + error {repo}/docs/index.md:3 Button directive must contain only a single Markdown link. Use: :::{button} + [text](url) + ::: + Or: :::{button} + [text][ref] + ::: + """ + ); } [InheritsTests] @@ -475,7 +512,22 @@ [Link One](/one) and [Link Two](/two) { [Test] public void EmitsErrorForMultipleLinks() => - Collector.Diagnostics.Should().ContainSingle(d => d.Message.Contains("must contain only a single Markdown link")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + """ + error {repo}/docs/index.md:3 Button directive must contain only a single Markdown link. Use: :::{button} + [text](url) + ::: + Or: :::{button} + [text][ref] + ::: + error {repo}/docs/index.md:4 `/one` does not exist. If it was recently removed add a redirect. resolved to `{repo}/docs/one` + error {repo}/docs/index.md:4 `/two` does not exist. If it was recently removed add a redirect. resolved to `{repo}/docs/two` + """ + ); } [InheritsTests] @@ -489,5 +541,19 @@ This is nested { [Test] public void EmitsErrorForNestedDirective() => - Collector.Diagnostics.Should().ContainSingle(d => d.Message.Contains("cannot contain nested directives")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + """ + error {repo}/docs/index.md:3 Button directive cannot contain nested directives or blocks. Use only a single Markdown link. + error {repo}/docs/index.md:3 Button directive requires a link. Use: :::{button} + [text](url) + ::: + Or: :::{button} + [text][ref] + ::: + """ + ); } diff --git a/tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs b/tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs index a43c1d1cb3..fa53b87102 100644 --- a/tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/CsvIncludeTests.cs @@ -8,6 +8,7 @@ using Elastic.Documentation.FileSystems; using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.CsvInclude; +using Nullean.Snapshots; using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -219,11 +220,12 @@ public class CsvIncludeNotFoundTests() : DirectiveTest(""" public void ReportsFileNotFound() => Block!.Found.Should().BeFalse(); [Test] - public void EmitsErrorForMissingFile() - { - Collector.Diagnostics.Should().NotBeNullOrEmpty(); - Collector.Diagnostics.Should().OnlyContain(d => d.Message.Contains("does not exist")); - } + public void EmitsErrorForMissingFile() => + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot("error {repo}/docs/index.md:3 CSV file `{repo}/docs/missing-file.csv` does not exist."); } [InheritsTests] @@ -233,9 +235,10 @@ public class CsvIncludeNoArgumentTests() : DirectiveTest(""" """) { [Test] - public void EmitsErrorForMissingArgument() - { - Collector.Diagnostics.Should().NotBeNullOrEmpty(); - Collector.Diagnostics.Should().OnlyContain(d => d.Message.Contains("requires an argument")); - } + public void EmitsErrorForMissingArgument() => + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot("error {repo}/docs/index.md:3 csv-include requires an argument specifying the path to the CSV file."); } diff --git a/tests/Elastic.Markdown.Tests/Directives/ImageCarouselTests.cs b/tests/Elastic.Markdown.Tests/Directives/ImageCarouselTests.cs index 7ac6cf9d78..92a519763d 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ImageCarouselTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ImageCarouselTests.cs @@ -5,7 +5,9 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Image; +using Nullean.Snapshots; namespace Elastic.Markdown.Tests.Directives; @@ -52,7 +54,7 @@ public void ProcessesNestedImages() public void AllImagesFoundSoNoErrorIsEmitted() { Block!.Images.Should().AllSatisfy(img => img.Found.Should().BeTrue()); - Collector.Diagnostics.Count.Should().Be(0); + Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } } @@ -75,7 +77,7 @@ public class ImageCarouselWithSmallHeightTests() : DirectiveTest d.Severity == Severity.Error); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + "error {repo}/docs/index.md:5 `img/missing.png` does not exist. resolved to `Nullean.ScopedFileSystem.ScopedFileInfo" + ); } } diff --git a/tests/Elastic.Markdown.Tests/Directives/ImageTests.cs b/tests/Elastic.Markdown.Tests/Directives/ImageTests.cs index 24576e6402..ca0389a0de 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ImageTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ImageTests.cs @@ -5,7 +5,9 @@ using System.IO.Abstractions.TestingHelpers; using AwesomeAssertions; using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Image; +using Nullean.Snapshots; namespace Elastic.Markdown.Tests.Directives; @@ -38,7 +40,7 @@ public void ParsesBreakPoint() public void ImageIsFoundSoNoErrorIsEmitted() { Block!.Found.Should().BeTrue(); - Collector.Diagnostics.Count.Should().Be(0); + Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } } @@ -88,7 +90,7 @@ public class AllowedExternalHostTests() : DirectiveTest( public void AllowedHostDoesNotWarn() { Block!.Found.Should().BeTrue(); - Collector.Diagnostics.Count.Should().Be(0); + Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } } @@ -113,6 +115,12 @@ public void WarnsOnExternalUri() { Block!.Found.Should().BeTrue(); - Collector.Diagnostics.Should().HaveCount(1).And.OnlyContain(d => d.Severity == Severity.Warning); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + "warning {repo}/docs/index.md:3 image is using an external URI: https://github.com/rowanc1/pics/blob/main/sunset.png?raw=true " + ); } } diff --git a/tests/Elastic.Markdown.Tests/Directives/MathBlockTests.cs b/tests/Elastic.Markdown.Tests/Directives/MathBlockTests.cs index 6fb0bcfd55..4c3f87cf66 100644 --- a/tests/Elastic.Markdown.Tests/Directives/MathBlockTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/MathBlockTests.cs @@ -6,6 +6,7 @@ using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Math; +using Nullean.Snapshots; using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -91,15 +92,8 @@ public class MathBlockEmptyTests() : DirectiveTest(""" """) { [Test] - public void EmptyContentGeneratesError() => Collector.Errors.Should().Be(1); - - [Test] - public void EmitsErrorForEmptyContent() - { - Collector.Diagnostics.Should().NotBeNullOrEmpty().And.HaveCount(1); - Collector.Diagnostics.Should().OnlyContain(d => d.Severity == Severity.Error); - Collector.Diagnostics.Should().OnlyContain(d => d.Message.StartsWith("Math directive requires content.")); - } + public void EmptyContentGeneratesError() => + Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot("error {repo}/docs/index.md:3 Math directive requires content."); } [InheritsTests] diff --git a/tests/Elastic.Markdown.Tests/Directives/MermaidTests.cs b/tests/Elastic.Markdown.Tests/Directives/MermaidTests.cs index c7b7f07b89..e66df61a3a 100644 --- a/tests/Elastic.Markdown.Tests/Directives/MermaidTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/MermaidTests.cs @@ -246,7 +246,14 @@ class A elasticBlue ) { [Test] - public void EmitsHints() => Collector.Diagnostics.Should().NotBeEmpty(); + public void EmitsHints() => + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + "hint {repo}/docs/index.md:3 Mermaid strict mode stripped 3 item(s): ClassDefDirective (line 3), UnknownClassReference (line 4), StyleDirective (line 5)" + ); [Test] public void EmitsSvgFile() => ReadMermaidSvgs().Should().NotBeEmpty(); @@ -293,7 +300,7 @@ public void RendersHtml() => ); [Test] - public void EmitsNoDiagnostics() => Collector.Diagnostics.Should().BeEmpty(); + public void EmitsNoDiagnostics() => Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); [Test] public void SvgContainsWarningFillColor() => ReadMermaidSvgs()[0].Should().Contain("#fdf3d8"); @@ -326,7 +333,7 @@ public void RendersHtml() => ); [Test] - public void EmitsNoDiagnostics() => Collector.Diagnostics.Should().BeEmpty(); + public void EmitsNoDiagnostics() => Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); [Test] public void UsesThemePalette() => ReadMermaidSvgs()[0].Should().Contain("#3788ff"); // blue-elastic-70 diff --git a/tests/Elastic.Markdown.Tests/Directives/PageCardBlockTests.cs b/tests/Elastic.Markdown.Tests/Directives/PageCardBlockTests.cs index d9eaf63e1e..9cca34c73c 100644 --- a/tests/Elastic.Markdown.Tests/Directives/PageCardBlockTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/PageCardBlockTests.cs @@ -6,8 +6,10 @@ using AwesomeAssertions; using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Navigation; +using Elastic.Documentation.Testing; using Elastic.Markdown.IO; using Elastic.Markdown.Myst.Directives.PageCard; +using Nullean.Snapshots; namespace Elastic.Markdown.Tests.Directives; @@ -30,7 +32,7 @@ protected override void AddToFileSystem(MockFileSystem fileSystem) => public void ResolvesUrlViaNavigationLookup() => Block!.ResolvedUrl.Should().Be("/other-page"); [Test] - public void EmitsNoErrors() => Collector.Diagnostics.Should().NotContain(d => d.Severity == Severity.Error); + public void EmitsNoErrors() => Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } /// @@ -56,7 +58,7 @@ protected override void ConfigureDocumentationSet(DocumentationSet set) => public void ResolvesUrlWithRehomedPrefix() => Block!.ResolvedUrl.Should().Be("/reference/rehomed/other-page"); [Test] - public void EmitsNoErrors() => Collector.Diagnostics.Should().NotContain(d => d.Severity == Severity.Error); + public void EmitsNoErrors() => Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } /// @@ -81,7 +83,7 @@ protected override void ConfigureDocumentationSet(DocumentationSet set) => public void ResolvesUrlWithRehomedPrefixAndAnchor() => Block!.ResolvedUrl.Should().Be("/reference/rehomed/other-page#install"); [Test] - public void EmitsNoErrors() => Collector.Diagnostics.Should().NotContain(d => d.Severity == Severity.Error); + public void EmitsNoErrors() => Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } /// Namespace-style link (no .md extension) probes for /index.md variant. @@ -98,7 +100,7 @@ protected override void AddToFileSystem(MockFileSystem fileSystem) => public void ResolvesIndexPageUrl() => Block!.ResolvedUrl.Should().Be("/subdir"); [Test] - public void EmitsNoErrors() => Collector.Diagnostics.Should().NotContain(d => d.Severity == Severity.Error); + public void EmitsNoErrors() => Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } /// Absolute HTTP URLs are rejected with an error. @@ -112,5 +114,11 @@ public class PageCardAbsoluteUrlErrorTests() : DirectiveTest( { [Test] public void EmitsErrorForAbsoluteUrl() => - Collector.Diagnostics.Should().Contain(d => d.Severity == Severity.Error && d.Message.Contains("absolute URL")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + "error {repo}/docs/index.md:3 page-card url must be a local .md path or crosslink, not an absolute URL: https://example.com/page" + ); } diff --git a/tests/Elastic.Markdown.Tests/Directives/RelatedLearningTests.cs b/tests/Elastic.Markdown.Tests/Directives/RelatedLearningTests.cs index 08d997ac22..ef9405c8dd 100644 --- a/tests/Elastic.Markdown.Tests/Directives/RelatedLearningTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/RelatedLearningTests.cs @@ -4,7 +4,9 @@ using AwesomeAssertions; using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.RelatedLearning; +using Nullean.Snapshots; namespace Elastic.Markdown.Tests.Directives; @@ -58,7 +60,7 @@ public void PageTocIncludesDefaultSlug() } [Test] - public void EmitsNoDiagnostics() => Collector.Diagnostics.Should().BeEmpty(); + public void EmitsNoDiagnostics() => Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); private static int CountOccurrences(string haystack, string needle) { @@ -137,8 +139,9 @@ public void EmitsErrorAndRendersNothing() { Collector .Diagnostics + .ToSnapshotText() .Should() - .Contain(d => d.Severity == Severity.Error && d.Message.Contains("unknown catalog ID 'not-a-module'")); + .MatchInlineSnapshot("error {repo}/docs/index.md:3 {related-learning} unknown catalog ID 'not-a-module'."); Block!.Items.Should().BeEmpty(); Html.Should().NotContain("related-learning"); File.PageTableOfContent.Should().NotContainKey(RelatedLearningBlock.DefaultSlug); @@ -159,8 +162,9 @@ public void WarnsAndKeepsFirstOccurrence() Block!.Items.Should().ContainSingle().Which.Id.Should().Be("apm-with-elastic"); Collector .Diagnostics + .ToSnapshotText() .Should() - .Contain(d => d.Severity == Severity.Warning && d.Message.Contains("duplicate catalog ID 'apm-with-elastic'")); + .MatchInlineSnapshot("warning {repo}/docs/index.md:3 {related-learning} duplicate catalog ID 'apm-with-elastic' was skipped."); } } @@ -175,8 +179,9 @@ public void EmitsErrorWhenArgumentMissing() { Collector .Diagnostics + .ToSnapshotText() .Should() - .Contain(d => d.Severity == Severity.Error && d.Message.Contains("requires at least one catalog ID as an argument")); + .MatchInlineSnapshot("error {repo}/docs/index.md:3 {related-learning} requires at least one catalog ID as an argument."); Block!.Items.Should().BeEmpty(); } } diff --git a/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs b/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs index 53aef69e8a..19e43d49cd 100644 --- a/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs @@ -8,6 +8,7 @@ using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Storybook; +using Nullean.Snapshots; using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -163,8 +164,21 @@ public class StorybookDisallowedRegistryVariableTests() : StorybookRegistryTest( public void WarnsAndLeavesExpressionLiteral() => Collector .Diagnostics + .ToSnapshotText() .Should() - .Contain(d => d.Severity == Severity.Warning && d.Message.Contains("not allow-listed for interpolation")); + .MatchInlineSnapshot( + """ + warning {repo}/docs/docset.yml 'storybook.registry' references environment variable 'AWS_SECRET_ACCESS_KEY' which is not allow-listed for interpolation and is left literal. Allowed: KIBANA_STORYBOOK_REGISTRY. + error {repo}/docs/index.md:3 storybook registry could not be read: ${AWS_SECRET_ACCESS_KEY:-docs_registry.json} + System.IO.FileNotFoundException: Could not find file '{repo}/docs/${AWS_SECRET_ACCESS_KEY:-docs_registry.json}'. + File name: '{repo}/docs/${AWS_SECRET_ACCESS_KEY:-docs_registry.json}' + at System.IO.Abstractions.TestingHelpers.MockFile.ReadAllText(String path, Encoding encoding) + at System.IO.Abstractions.TestingHelpers.MockFile.ReadAllText(String path) + at Nullean.ScopedFileSystem.ScopedFile.ReadAllText(String path) + at Elastic.Markdown.Myst.Directives.Storybook.StorybookBlock.ReadRegistry(String rawRegistry) in {repo}/src/Elastic.Markdown/Myst/Directives/Storybook/StorybookBlock.cs:line 209 + at Elastic.Markdown.Myst.Directives.Storybook.StorybookBlock.TryReadRegistry(String rawRegistry, String& registryJson, Exception& error) in {repo}/src/Elastic.Markdown/Myst/Directives/Storybook/StorybookBlock.cs:line 184 + """ + ); } [InheritsTests] @@ -198,8 +212,11 @@ public class StorybookStructuredReferenceWrongStorybookTests() : StorybookRegist public void DoesNotFallbackToAnotherStorybook() => Collector .Diagnostics + .ToSnapshotText() .Should() - .Contain(d => d.Message.Contains("does not contain id 'kibana:content_management:ai-components-aibutton--default'")); + .MatchInlineSnapshot( + "error {repo}/docs/index.md:3 storybook registry does not contain id 'kibana:content_management:ai-components-aibutton--default'." + ); } [InheritsTests] @@ -265,8 +282,11 @@ public void WarnsAndFallsBackToDefaultHeight() Block!.Height.Should().Be(400); Collector .Diagnostics + .ToSnapshotText() .Should() - .ContainSingle(d => d.Severity == Severity.Warning && d.Message.Contains(":height: must be a positive integer")); + .MatchInlineSnapshot( + "warning {repo}/docs/index.md:3 storybook directive :height: must be a positive integer. Got 'tall', using default 400px." + ); Html.Should().MatchInlineHtmlSnapshot( html: DocsHtml.Body(), expected: """ @@ -288,7 +308,12 @@ public class StorybookMissingRegistryTests() : DirectiveTest( ) { [Test] - public void EmitsError() => Collector.Diagnostics.Should().Contain(d => d.Message.Contains("requires docset.yml storybook.registry")); + public void EmitsError() => + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot("error {repo}/docs/index.md:3 storybook directive requires docset.yml storybook.registry."); } [InheritsTests] @@ -298,7 +323,12 @@ public class StorybookMissingIdTests() : StorybookRegistryTest(""" """) { [Test] - public void EmitsError() => Collector.Diagnostics.Should().Contain(d => d.Message.Contains("requires :id: or :project:")); + public void EmitsError() => + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot("error {repo}/docs/index.md:3 storybook directive requires :id: or :project:."); } [InheritsTests] @@ -314,6 +344,7 @@ public class StorybookPositionalArgumentWarningTests() : StorybookRegistryTest( public void EmitsWarning() => Collector .Diagnostics + .ToSnapshotText() .Should() - .ContainSingle(d => d.Severity == Severity.Warning && d.Message.Contains("ignores positional arguments")); + .MatchInlineSnapshot("warning {repo}/docs/index.md:3 storybook directive ignores positional arguments. Use properties instead."); } diff --git a/tests/Elastic.Markdown.Tests/Directives/TableDirectiveTests.cs b/tests/Elastic.Markdown.Tests/Directives/TableDirectiveTests.cs index f69ed7901e..9f075867c9 100644 --- a/tests/Elastic.Markdown.Tests/Directives/TableDirectiveTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/TableDirectiveTests.cs @@ -6,6 +6,7 @@ using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives.Table; +using Nullean.Snapshots; using Nullean.Snapshots.Html; namespace Elastic.Markdown.Tests.Directives; @@ -338,7 +339,11 @@ public class TableDirectiveWidthCountMismatchTests() : DirectiveTest - Collector.Diagnostics.Should().Contain(d => d.Severity == Severity.Error && d.Message.Contains("does not match")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot("error {repo}/docs/index.md:3 Column width count (3) does not match table column count (2)."); } [InheritsTests] @@ -356,7 +361,11 @@ public class TableDirectiveWidthsSumErrorTests() : DirectiveTest - Collector.Diagnostics.Should().Contain(d => d.Severity == Severity.Error && d.Message.Contains("sum to 12")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot("error {repo}/docs/index.md:3 Column widths must sum to 12 (Bootstrap grid). Got sum of 8."); } [InheritsTests] @@ -370,7 +379,13 @@ public class TableDirectiveNoTableTests() : DirectiveTest(" { [Test] public void EmitsError() => - Collector.Diagnostics.Should().Contain(d => d.Severity == Severity.Error && d.Message.Contains("pipe table")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + "error {repo}/docs/index.md:3 Table directive must contain a pipe table. Add a table using Markdown pipe syntax." + ); } [InheritsTests] @@ -388,7 +403,13 @@ public class TableDirectiveInvalidWidthsTests() : DirectiveTest - Collector.Diagnostics.Should().Contain(d => d.Severity == Severity.Error && d.Message.Contains("Invalid widths value")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + "error {repo}/docs/index.md:3 Invalid widths value 'foo'. Use preset (auto, description) or dash-separated integers 1-12 that sum to 12 (e.g., 4-8, 4-4-4)." + ); } [InheritsTests] @@ -406,7 +427,13 @@ public class TableDirectiveOutOfRangeWidthsTests() : DirectiveTest - Collector.Diagnostics.Should().Contain(d => d.Severity == Severity.Error && d.Message.Contains("Invalid widths value")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + "error {repo}/docs/index.md:3 Invalid widths value '0-12'. Use preset (auto, description) or dash-separated integers 1-12 that sum to 12 (e.g., 4-8, 4-4-4)." + ); } [InheritsTests] @@ -428,5 +455,9 @@ public class TableDirectiveMultipleTablesTests() : DirectiveTest - Collector.Diagnostics.Should().Contain(d => d.Severity == Severity.Error && d.Message.Contains("exactly one pipe table")); + Collector + .Diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot("error {repo}/docs/index.md:3 Table directive must contain exactly one pipe table."); } diff --git a/tests/Elastic.Markdown.Tests/Directives/UnsupportedTests.cs b/tests/Elastic.Markdown.Tests/Directives/UnsupportedTests.cs index 4f1189197a..b5f6861937 100644 --- a/tests/Elastic.Markdown.Tests/Directives/UnsupportedTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/UnsupportedTests.cs @@ -4,7 +4,9 @@ using AwesomeAssertions; using Elastic.Documentation.Diagnostics; +using Elastic.Documentation.Testing; using Elastic.Markdown.Myst.Directives; +using Nullean.Snapshots; namespace Elastic.Markdown.Tests.Directives; @@ -27,15 +29,7 @@ A regular paragraph. public void SetsCorrectDirectiveType() => Block!.Directive.Should().Be(directive); [Test] - public void TracksASingleWarning() => Collector.Warnings.Should().Be(1); - - [Test] - public void EmitsUnsupportedWarnings() - { - Collector.Diagnostics.Should().NotBeNullOrEmpty().And.HaveCount(1); - Collector.Diagnostics.Should().OnlyContain(d => d.Severity == Severity.Warning); - Collector.Diagnostics.Should().OnlyContain(d => d.Message.StartsWith($"Directive block '{directive}' is unsupported.")); - } + public void EmitsUnsupportedWarnings() => Collector.Diagnostics.ToSnapshotText().Should().MatchSnapshot(); } [InheritsTests] diff --git a/tests/Elastic.Markdown.Tests/Directives/VersionTests.cs b/tests/Elastic.Markdown.Tests/Directives/VersionTests.cs index 57a7990842..fc5c9db91e 100644 --- a/tests/Elastic.Markdown.Tests/Directives/VersionTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/VersionTests.cs @@ -4,8 +4,10 @@ using AwesomeAssertions; using Elastic.Documentation; +using Elastic.Documentation.Testing; using Elastic.Documentation.Versions; using Elastic.Markdown.Myst.Directives.Version; +using Nullean.Snapshots; namespace Elastic.Markdown.Tests.Directives; @@ -74,7 +76,7 @@ public class SimpleVersion() : VersionValidationTests("7.17") public void SetsVersion() => Block!.Version.Should().Be(new SemVersion(7, 17, 0)); [Test] - public void HasNoError() => Collector.Diagnostics.Should().BeEmpty(); + public void HasNoError() => Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot(""); } [InheritsTests] @@ -82,7 +84,7 @@ public class MajorVersionOnly() : VersionValidationTests("8") { [Test] public void HasError() => - Collector.Diagnostics.Should().HaveCount(1).And.Contain(d => d.Message.Contains("'8' is not a valid version")); + Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot("error {repo}/docs/index.md:3 '8' is not a valid version"); } [InheritsTests] @@ -90,5 +92,5 @@ public class BranchVersion() : VersionValidationTests("8.x") { [Test] public void HasError() => - Collector.Diagnostics.Should().HaveCount(1).And.Contain(d => d.Message.Contains("'8.x' is not a valid version")); + Collector.Diagnostics.ToSnapshotText().Should().MatchInlineSnapshot("error {repo}/docs/index.md:3 '8.x' is not a valid version"); } From e3ff2669b3ae29739d5ece8b4ae66ebed0ace856 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 14:09:32 +0100 Subject: [PATCH 14/18] Scrub paths the same way on every operating system The repository and temp folder scrubbers replaced only the root, so a Windows run printed {repo}\docs\index.md where the verified text says {repo}/docs/index.md, and about 160 diagnostics snapshots failed there. The path that follows the root is now written with forward slashes. The temp scrubber also swallowed everything up to the next whitespace, including the :line after a file name. A path now ends at a colon too, and the repository root is tried before the temp root because the repository can sit under the temp folder. The doc comment claimed it scrubbed git SHAs; it does not, and no longer says so. Tested with a Windows-style root, which also runs on Linux and macOS. Removing the separator replacement makes that test fail. Co-Authored-By: Claude Sonnet 5.5 --- .../Linked/SnapshotScrubbing.cs | 29 ++++++++++++------- .../Testing/SnapshotHelpersTests.cs | 15 ++++++++++ 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/tests-shared/Elastic.Documentation.Testing/Linked/SnapshotScrubbing.cs b/tests-shared/Elastic.Documentation.Testing/Linked/SnapshotScrubbing.cs index 6a80bb71fc..44132cd7bd 100644 --- a/tests-shared/Elastic.Documentation.Testing/Linked/SnapshotScrubbing.cs +++ b/tests-shared/Elastic.Documentation.Testing/Linked/SnapshotScrubbing.cs @@ -9,8 +9,9 @@ namespace Elastic.Documentation.Testing.Linked; /// -/// Scrubbers every snapshot in docs-builder's tests gets: machine-specific paths and git SHAs never belong in a -/// verified file. Runs when any test project referencing this assembly loads it. +/// Scrubbers every snapshot in docs-builder's tests gets: the repository root and the temp folder are replaced by +/// {repo} and {temp}, and the rest of such a path is written with forward slashes, so a verified file +/// reads the same on every machine and operating system. Compiled into each test project. /// internal static class SnapshotScrubbing { @@ -19,14 +20,22 @@ internal static class SnapshotScrubbing #pragma warning restore CA2255 internal static void Register() { - SnapshotDefaults.AddScrubber( - Scrubbers.Regex( - new Regex(Regex.Escape(Path.GetTempPath().TrimEnd(Path.DirectorySeparatorChar)) + @"[^\s""'<>]*", RegexOptions.Compiled), - "{temp}" - ) - ); + // the more specific root first: the repository may sit under the temp folder if (FindSolutionRoot() is { } root) - SnapshotDefaults.AddScrubber(Scrubbers.Regex(new Regex(Regex.Escape(root), RegexOptions.Compiled), "{repo}")); + SnapshotDefaults.AddScrubber(Under(root, "{repo}")); + SnapshotDefaults.AddScrubber(Under(Path.GetTempPath(), "{temp}")); + } + + /// + /// Replaces with and writes the path that follows it with + /// forward slashes. The path ends at whitespace, a quote, an angle bracket or a colon, so file.md:3 keeps its line. + /// + internal static Func Under(string root, string token) + { + root = root.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + var roots = new[] { root, root.Replace('\\', '/') }.Distinct().Select(Regex.Escape); + var pattern = new Regex($"(?:{string.Join('|', roots)})(?[^\\s\"'<>:*?|]*)", RegexOptions.Compiled); + return text => pattern.Replace(text, m => token + m.Groups["rest"].Value.Replace('\\', '/')); } private static string? FindSolutionRoot() @@ -34,6 +43,6 @@ internal static void Register() var dir = new DirectoryInfo(AppContext.BaseDirectory); while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "docs-builder.slnx"))) dir = dir.Parent; - return dir?.FullName.TrimEnd(Path.DirectorySeparatorChar); + return dir?.FullName; } } diff --git a/tests/Elastic.Documentation.Tests/Testing/SnapshotHelpersTests.cs b/tests/Elastic.Documentation.Tests/Testing/SnapshotHelpersTests.cs index 5897c67b4a..bef3f63064 100644 --- a/tests/Elastic.Documentation.Tests/Testing/SnapshotHelpersTests.cs +++ b/tests/Elastic.Documentation.Tests/Testing/SnapshotHelpersTests.cs @@ -6,6 +6,7 @@ using AwesomeAssertions; using Elastic.Documentation.Diagnostics; using Elastic.Documentation.Testing; +using Elastic.Documentation.Testing.Linked; using Nullean.Snapshots; using Nullean.Snapshots.Html; @@ -67,4 +68,18 @@ public void DocsHtmlSection_DropsFixtureHeadingAndHtmxAttributes() => """, DocsHtml.Section() ); + + [Test] + public void PathScrubber_WindowsRoot_WritesTheRestWithForwardSlashesAndKeepsTheLine() => + SnapshotScrubbing + .Under(@"D:\a\docs-builder\docs-builder", "{repo}")(@"error D:\a\docs-builder\docs-builder\docs\index.md:3 bad link") + .Should() + .MatchInlineSnapshot("error {repo}/docs/index.md:3 bad link"); + + [Test] + public void PathScrubber_UnixRootInsideText_LeavesOtherPathsAlone() => + SnapshotScrubbing + .Under("/work/repo/", "{repo}")("see /work/repo/docs/a.md and /elsewhere/b.md") + .Should() + .MatchInlineSnapshot("see {repo}/docs/a.md and /elsewhere/b.md"); } From 12c95f57cc576994e27cfe2084ffb1e61dc50507 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 14:12:40 +0100 Subject: [PATCH 15/18] Scrub random folder names under the temp directory A test that works in a Guid-named folder in the temp directory printed that name in its diagnostics, so a snapshot of them could not be stable. The temp scrubber now replaces a run of 32 hex digits in the path with {id}. Co-Authored-By: Claude Sonnet 5.5 --- .../Linked/SnapshotScrubbing.cs | 14 +++++++++++--- .../Testing/SnapshotHelpersTests.cs | 9 +++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/tests-shared/Elastic.Documentation.Testing/Linked/SnapshotScrubbing.cs b/tests-shared/Elastic.Documentation.Testing/Linked/SnapshotScrubbing.cs index 44132cd7bd..7c97d0d9b1 100644 --- a/tests-shared/Elastic.Documentation.Testing/Linked/SnapshotScrubbing.cs +++ b/tests-shared/Elastic.Documentation.Testing/Linked/SnapshotScrubbing.cs @@ -23,21 +23,29 @@ internal static void Register() // the more specific root first: the repository may sit under the temp folder if (FindSolutionRoot() is { } root) SnapshotDefaults.AddScrubber(Under(root, "{repo}")); - SnapshotDefaults.AddScrubber(Under(Path.GetTempPath(), "{temp}")); + // a test folder in the temp directory is usually named after a fresh Guid; that name is not part of what is verified + SnapshotDefaults.AddScrubber(Under(Path.GetTempPath(), "{temp}", RandomName)); } /// /// Replaces with and writes the path that follows it with /// forward slashes. The path ends at whitespace, a quote, an angle bracket or a colon, so file.md:3 keeps its line. /// - internal static Func Under(string root, string token) + internal static Func Under(string root, string token, Regex? randomName = null) { root = root.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); var roots = new[] { root, root.Replace('\\', '/') }.Distinct().Select(Regex.Escape); var pattern = new Regex($"(?:{string.Join('|', roots)})(?[^\\s\"'<>:*?|]*)", RegexOptions.Compiled); - return text => pattern.Replace(text, m => token + m.Groups["rest"].Value.Replace('\\', '/')); + return text => pattern.Replace(text, m => + { + var rest = m.Groups["rest"].Value.Replace('\\', '/'); + return token + (randomName is null ? rest : randomName.Replace(rest, "{id}")); + }); } + /// A run of 32 hex digits: a Guid.ToString("N") used as a folder name. + private static readonly Regex RandomName = new("[0-9a-fA-F]{32}", RegexOptions.Compiled); + private static string? FindSolutionRoot() { var dir = new DirectoryInfo(AppContext.BaseDirectory); diff --git a/tests/Elastic.Documentation.Tests/Testing/SnapshotHelpersTests.cs b/tests/Elastic.Documentation.Tests/Testing/SnapshotHelpersTests.cs index bef3f63064..21b3ae758a 100644 --- a/tests/Elastic.Documentation.Tests/Testing/SnapshotHelpersTests.cs +++ b/tests/Elastic.Documentation.Tests/Testing/SnapshotHelpersTests.cs @@ -82,4 +82,13 @@ public void PathScrubber_UnixRootInsideText_LeavesOtherPathsAlone() => .Under("/work/repo/", "{repo}")("see /work/repo/docs/a.md and /elsewhere/b.md") .Should() .MatchInlineSnapshot("see {repo}/docs/a.md and /elsewhere/b.md"); + + [Test] + public void TempScrubber_GuidFolderName_IsReplacedSoTheSnapshotIsStable() => + SnapshotScrubbing + .Under("/tmp", "{temp}", new System.Text.RegularExpressions.Regex("[0-9a-fA-F]{32}"))( + "error /tmp/diff-validate-test-1d248de3468f4d58849398ff8b320802/docs/redirects.yml Missing" + ) + .Should() + .MatchInlineSnapshot("error {temp}/diff-validate-test-{id}/docs/redirects.yml Missing"); } From 021c3c0f9588533105b2572ba69aacd38ce1360f Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 14:28:53 +0100 Subject: [PATCH 16/18] Make diagnostics snapshots and snapshot paths the same on every operating system CI on Windows failed five of the stacked PRs for reasons a laptop never shows: - DiagnosticsSnapshot now writes paths with forward slashes and drops a leading drive letter, so `C:\docs\toc.yml`, `C:/docs/toc.yml` and `/docs/toc.yml` read the same, and `testing\req.md` inside a message reads `testing/req.md`. - ToSnapshotText(firstLineOnly: true) keeps only the first line of each message, for diagnostics that carry an exception: its stack trace has source line numbers and the exception type differs per platform. - SnapshotPathLengthTests fails when a verified snapshot's path, with the received file's longer folder and the CI checkout root, would pass Windows' 260 characters. git on the runner cannot check such a file out, so the whole job failed before any test ran. Each has a test. Lowering the length limit makes the guard fail. Co-Authored-By: Claude Sonnet 5.5 --- .../DiagnosticsSnapshot.cs | 29 +++++++-- .../Testing/SnapshotHelpersTests.cs | 43 +++++++++++++ .../Testing/SnapshotPathLengthTests.cs | 60 +++++++++++++++++++ 3 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 tests/Elastic.Documentation.Tests/Testing/SnapshotPathLengthTests.cs diff --git a/tests-shared/Elastic.Documentation.Testing/DiagnosticsSnapshot.cs b/tests-shared/Elastic.Documentation.Testing/DiagnosticsSnapshot.cs index 0e8f94f1f2..f53161eea3 100644 --- a/tests-shared/Elastic.Documentation.Testing/DiagnosticsSnapshot.cs +++ b/tests-shared/Elastic.Documentation.Testing/DiagnosticsSnapshot.cs @@ -3,17 +3,28 @@ // See the LICENSE file in the project root for more information using System.Text; +using System.Text.RegularExpressions; using Elastic.Documentation.Diagnostics; namespace Elastic.Documentation.Testing; -public static class DiagnosticsSnapshot +public static partial class DiagnosticsSnapshot { /// /// One line per diagnostic, error|warning|hint file:line message, in the order they were written. Empty when /// there are none, so a clean run reads as MatchInlineSnapshot(""). + /// + /// Paths read the same on every operating system: backslashes become forward slashes and a leading drive letter is + /// dropped, so C:\docs\toc.yml, C:/docs/toc.yml and /docs/toc.yml are the same text. A message that + /// really contains a backslash reads with a slash, on Linux too. + /// /// - public static string ToSnapshotText(this IEnumerable diagnostics) + /// What a test's collector holds. + /// + /// Keep only the first line of each message. For diagnostics that carry an exception, whose stack trace (frames and + /// source line numbers) is no part of what a test verifies, and whose type and text can differ per platform. + /// + public static string ToSnapshotText(this IEnumerable diagnostics, bool firstLineOnly = false) { var sb = new StringBuilder(); foreach (var d in diagnostics) @@ -24,9 +35,19 @@ public static string ToSnapshotText(this IEnumerable diagnostics) Severity.Warning => "warning", _ => "hint" }; - var position = d.Line is { } line ? $"{d.File}:{line}" : d.File; - sb.Append(severity).Append(' ').Append(position).Append(' ').Append(d.Message).Append('\n'); + var file = Normalise(d.File); + var position = d.Line is { } line ? $"{file}:{line}" : file; + var message = d.Message ?? string.Empty; + if (firstLineOnly) + message = message.Split('\n')[0].TrimEnd('\r'); + _ = sb.Append(severity).Append(' ').Append(position).Append(' ').Append(Normalise(message)).Append('\n'); } return sb.ToString(); } + + private static string Normalise(string text) => DriveLetter().Replace(text.Replace('\\', '/'), string.Empty); + + // "C:/docs/..." at the start of a path; not the "s:" of "https://", which a letter or digit precedes + [GeneratedRegex("(? ) .Should() .MatchInlineSnapshot("error {temp}/diff-validate-test-{id}/docs/redirects.yml Missing"); + + [Test] + public void ToSnapshotText_WindowsPaths_ReadLikeUnixPaths() + { + var diagnostics = new List + { + new() + { + Severity = Severity.Error, + File = @"C:\docs\section-b\toc.yml", + Message = @"`sub-requirements2` does not exist in testing\req.md." + }, + new() { Severity = Severity.Error, File = "C:/docs/docset.yml", Line = 4, Message = "see https://elastic.co/docs and D:/x" }, + new() { Severity = Severity.Warning, File = @"/docs\docset.yml", Message = "mixed" } + }; + diagnostics + .ToSnapshotText() + .Should() + .MatchInlineSnapshot( + """ + error /docs/section-b/toc.yml `sub-requirements2` does not exist in testing/req.md. + error /docs/docset.yml:4 see https://elastic.co/docs and /x + warning /docs/docset.yml mixed + """ + ); + } + + [Test] + public void ToSnapshotText_FirstLineOnly_DropsTheStackTrace() + { + var diagnostics = new List + { + new() + { + Severity = Severity.Error, + File = "index.md", + Line = 3, + Message = + "registry could not be read\nSystem.IO.FileNotFoundException: Could not find file 'x'.\n at Some.Method() in /src/File.cs:line 209" + } + }; + diagnostics.ToSnapshotText(firstLineOnly: true).Should().MatchInlineSnapshot("error index.md:3 registry could not be read"); + } } diff --git a/tests/Elastic.Documentation.Tests/Testing/SnapshotPathLengthTests.cs b/tests/Elastic.Documentation.Tests/Testing/SnapshotPathLengthTests.cs new file mode 100644 index 0000000000..647cddfb22 --- /dev/null +++ b/tests/Elastic.Documentation.Tests/Testing/SnapshotPathLengthTests.cs @@ -0,0 +1,60 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using AwesomeAssertions; + +namespace Elastic.Documentation.Tests.Testing; + +/// +/// A verified snapshot is named after its test class and method, and a failed run writes the received file under +/// .artifacts/.snapshots/received/{Project}/, a longer path. Windows stops at 260 characters, and git on the CI runner +/// cannot even check out a file that is too long, so the whole job fails before any test runs. +/// +public class SnapshotPathLengthTests +{ + // "D:\a\docs-builder\docs-builder\" is where a GitHub runner puts the checkout. + private const int CheckoutRootLength = 31; + private const string ReceivedFolder = ".artifacts/.snapshots/received/"; + // Windows' MAX_PATH is 260 including the terminating character; keep a margin. + private const int MaxLength = 250; + + [Test] + public void VerifiedSnapshots_FitInAWindowsPath_EvenWhenTheReceivedFileIsWritten() + { + var root = FindRepositoryRoot(); + var tooLong = new List(); + foreach (var testsFolder in new[] { "tests", "tests-integration" }) + { + var folder = Path.Combine(root, testsFolder); + if (!Directory.Exists(folder)) + continue; + foreach (var project in Directory.EnumerateDirectories(folder)) + { + var snapshots = Path.Combine(project, ".snapshots"); + if (!Directory.Exists(snapshots)) + continue; + foreach (var file in Directory.EnumerateFiles(snapshots, "*", SearchOption.AllDirectories)) + { + var relative = Path.GetRelativePath(snapshots, file).Replace('\\', '/'); + var length = CheckoutRootLength + ReceivedFolder.Length + Path.GetFileName(project).Length + 1 + relative.Length; + if (length > MaxLength) + tooLong.Add($"{length} {Path.GetRelativePath(root, file).Replace('\\', '/')}"); + } + } + } + + tooLong.Should().BeEmpty( + "a snapshot path over {0} characters cannot be checked out on Windows; shorten the test's name or its class", + MaxLength + ); + } + + private static string FindRepositoryRoot() + { + var dir = new DirectoryInfo(AppContext.BaseDirectory); + while (dir is not null && !File.Exists(Path.Combine(dir.FullName, "docs-builder.slnx"))) + dir = dir.Parent; + return dir?.FullName ?? throw new InvalidOperationException("docs-builder.slnx not found above the test binaries"); + } +} From b5cddeb4343e9dbf91530fd5196189039519971f Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 14:30:11 +0100 Subject: [PATCH 17/18] Keep exception internals out of the directive diagnostics snapshots Three snapshots held a .NET stack trace: the storybook registry error and the two malformed applies-switch YAML errors. A trace has source line numbers and library frames, so it breaks when StorybookBlock.cs is edited or SharpYaml is upgraded, and on Windows the storybook case throws a different exception altogether (a path with a colon is not a legal path there). They now snapshot the first line of each diagnostic. Co-Authored-By: Claude Sonnet 5.5 --- .../ApplicabilitySwitchMalformedYamlTests.cs | 104 ++---------------- .../Directives/StorybookTests.cs | 9 +- 2 files changed, 9 insertions(+), 104 deletions(-) diff --git a/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchMalformedYamlTests.cs b/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchMalformedYamlTests.cs index f29b947a7a..e324b99ce5 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchMalformedYamlTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ApplicabilitySwitchMalformedYamlTests.cs @@ -30,57 +30,13 @@ This item has an applies_to definition that is not valid YAML. public void ReportsTheDefinitionAsAnError() => Collector .Diagnostics - .ToSnapshotText() + .ToSnapshotText(firstLineOnly: true) .Should() .MatchInlineSnapshot( """ - error {repo}/docs/index.md:5 Unable to parse applies_to definition: stack: [ga - SharpYaml.SemanticErrorException: (Lin: 1, Col: 0, Chr: 10) - (Lin: 1, Col: 0, Chr: 10): While parsing a flow sequence, did not find expected ',' or ']'. - at SharpYaml.Parser`1.ParseFlowSequenceEntry(Boolean isFirst) - at SharpYaml.Parser`1.StateMachine() - at SharpYaml.Parser`1.MoveNext() - at SharpYaml.Serialization.YamlReader.YamlReaderState.ReadCore() - at SharpYaml.Serialization.YamlReader.YamlReaderState.Read() - at SharpYaml.Serialization.YamlReader.Read() - at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) - at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) - at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) - at Elastic.Documentation.AppliesTo.ApplicableToYamlConverter.ReadUntyped(YamlReader reader) in {repo}/src/Elastic.Documentation.Configuration/Converters/ApplicableToYamlConverter.cs:line 41 - at Elastic.Documentation.AppliesTo.ApplicableToYamlConverter.Read(YamlReader reader) in {repo}/src/Elastic.Documentation.Configuration/Converters/ApplicableToYamlConverter.cs:line 94 - at SharpYaml.Serialization.YamlConverter`1.Read(YamlReader reader, Type typeToConvert) - at Elastic.Markdown.Myst.DocsBuilderYamlStaticContext.ReadValue4(YamlReader reader, RuntimeCustomConverterCache runtimeConverters) in {repo}/.artifacts/obj/Elastic.Markdown/release/SharpYaml.SourceGenerator/SharpYaml.SourceGeneration.YamlSerializerContextGenerator/DocsBuilderYamlStaticContext.g.cs:line 2768 - at Elastic.Markdown.Myst.DocsBuilderYamlStaticContext.GeneratedTypeInfo4.Read(YamlReader reader) in {repo}/.artifacts/obj/Elastic.Markdown/release/SharpYaml.SourceGenerator/SharpYaml.SourceGeneration.YamlSerializerContextGenerator/DocsBuilderYamlStaticContext.g.cs:line 485 - at SharpYaml.YamlTypeInfo`1.ReadAsObject(YamlReader reader) - at SharpYaml.YamlSerializer.DeserializeCore(YamlTypeInfo typeInfo, String yaml) - at SharpYaml.YamlSerializer.Deserialize(String yaml, Type returnType, YamlSerializerOptions options) - at SharpYaml.YamlSerializer.Deserialize[T](String yaml, YamlSerializerOptions options) - at Elastic.Markdown.Myst.YamlSerialization.Deserialize[T](String yaml, ProductsConfiguration products, Func`2 configure) in {repo}/src/Elastic.Markdown/Myst/YamlSerialization.cs:line 59 - at Elastic.Markdown.Myst.YamlSerialization.Deserialize[T](String yaml, ProductsConfiguration products) in {repo}/src/Elastic.Markdown/Myst/YamlSerialization.cs:line 21 - at Elastic.Markdown.Myst.Directives.AppliesSwitch.AppliesItemBlock.ParseApplicableTo(String yaml) in {repo}/src/Elastic.Markdown/Myst/Directives/AppliesSwitch/AppliesSwitchBlock.cs:line 78 - error {repo}/docs/index.md:5 Unable to parse applies_to definition: stack: [ga - SharpYaml.SemanticErrorException: (Lin: 1, Col: 0, Chr: 10) - (Lin: 1, Col: 0, Chr: 10): While parsing a flow sequence, did not find expected ',' or ']'. - at SharpYaml.Parser`1.ParseFlowSequenceEntry(Boolean isFirst) - at SharpYaml.Parser`1.StateMachine() - at SharpYaml.Parser`1.MoveNext() - at SharpYaml.Serialization.YamlReader.YamlReaderState.ReadCore() - at SharpYaml.Serialization.YamlReader.YamlReaderState.Read() - at SharpYaml.Serialization.YamlReader.Read() - at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) - at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) - at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) - at Elastic.Documentation.AppliesTo.ApplicableToYamlConverter.ReadUntyped(YamlReader reader) in {repo}/src/Elastic.Documentation.Configuration/Converters/ApplicableToYamlConverter.cs:line 41 - at Elastic.Documentation.AppliesTo.ApplicableToYamlConverter.Read(YamlReader reader) in {repo}/src/Elastic.Documentation.Configuration/Converters/ApplicableToYamlConverter.cs:line 94 - at SharpYaml.Serialization.YamlConverter`1.Read(YamlReader reader, Type typeToConvert) - at Elastic.Markdown.Myst.DocsBuilderYamlStaticContext.ReadValue4(YamlReader reader, RuntimeCustomConverterCache runtimeConverters) in {repo}/.artifacts/obj/Elastic.Markdown/release/SharpYaml.SourceGenerator/SharpYaml.SourceGeneration.YamlSerializerContextGenerator/DocsBuilderYamlStaticContext.g.cs:line 2768 - at Elastic.Markdown.Myst.DocsBuilderYamlStaticContext.GeneratedTypeInfo4.Read(YamlReader reader) in {repo}/.artifacts/obj/Elastic.Markdown/release/SharpYaml.SourceGenerator/SharpYaml.SourceGeneration.YamlSerializerContextGenerator/DocsBuilderYamlStaticContext.g.cs:line 485 - at SharpYaml.YamlTypeInfo`1.ReadAsObject(YamlReader reader) - at SharpYaml.YamlSerializer.DeserializeCore(YamlTypeInfo typeInfo, String yaml) - at SharpYaml.YamlSerializer.Deserialize(String yaml, Type returnType, YamlSerializerOptions options) - at SharpYaml.YamlSerializer.Deserialize[T](String yaml, YamlSerializerOptions options) - at Elastic.Markdown.Myst.YamlSerialization.Deserialize[T](String yaml, ProductsConfiguration products, Func`2 configure) in {repo}/src/Elastic.Markdown/Myst/YamlSerialization.cs:line 59 - at Elastic.Markdown.Myst.YamlSerialization.Deserialize[T](String yaml, ProductsConfiguration products) in {repo}/src/Elastic.Markdown/Myst/YamlSerialization.cs:line 21 - at Elastic.Markdown.Myst.Directives.DirectiveHtmlRenderer.ParseApplicableTo(String yaml, DirectiveBlock block) in {repo}/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs:line 648 - """ + error {repo}/docs/index.md:5 Unable to parse applies_to definition: stack: [ga + error {repo}/docs/index.md:5 Unable to parse applies_to definition: stack: [ga + """ ); [Test] @@ -106,57 +62,13 @@ This item has an applies_to definition that is not valid YAML. public void ReportsTheDefinitionAsAnError() => Collector .Diagnostics - .ToSnapshotText() + .ToSnapshotText(firstLineOnly: true) .Should() .MatchInlineSnapshot( """ - error {repo}/docs/index.md:5 Unable to parse applies_to definition: stack: [ga - SharpYaml.SemanticErrorException: (Lin: 1, Col: 0, Chr: 10) - (Lin: 1, Col: 0, Chr: 10): While parsing a flow sequence, did not find expected ',' or ']'. - at SharpYaml.Parser`1.ParseFlowSequenceEntry(Boolean isFirst) - at SharpYaml.Parser`1.StateMachine() - at SharpYaml.Parser`1.MoveNext() - at SharpYaml.Serialization.YamlReader.YamlReaderState.ReadCore() - at SharpYaml.Serialization.YamlReader.YamlReaderState.Read() - at SharpYaml.Serialization.YamlReader.Read() - at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) - at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) - at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) - at Elastic.Documentation.AppliesTo.ApplicableToYamlConverter.ReadUntyped(YamlReader reader) in {repo}/src/Elastic.Documentation.Configuration/Converters/ApplicableToYamlConverter.cs:line 41 - at Elastic.Documentation.AppliesTo.ApplicableToYamlConverter.Read(YamlReader reader) in {repo}/src/Elastic.Documentation.Configuration/Converters/ApplicableToYamlConverter.cs:line 94 - at SharpYaml.Serialization.YamlConverter`1.Read(YamlReader reader, Type typeToConvert) - at Elastic.Markdown.Myst.DocsBuilderYamlStaticContext.ReadValue4(YamlReader reader, RuntimeCustomConverterCache runtimeConverters) in {repo}/.artifacts/obj/Elastic.Markdown/release/SharpYaml.SourceGenerator/SharpYaml.SourceGeneration.YamlSerializerContextGenerator/DocsBuilderYamlStaticContext.g.cs:line 2768 - at Elastic.Markdown.Myst.DocsBuilderYamlStaticContext.GeneratedTypeInfo4.Read(YamlReader reader) in {repo}/.artifacts/obj/Elastic.Markdown/release/SharpYaml.SourceGenerator/SharpYaml.SourceGeneration.YamlSerializerContextGenerator/DocsBuilderYamlStaticContext.g.cs:line 485 - at SharpYaml.YamlTypeInfo`1.ReadAsObject(YamlReader reader) - at SharpYaml.YamlSerializer.DeserializeCore(YamlTypeInfo typeInfo, String yaml) - at SharpYaml.YamlSerializer.Deserialize(String yaml, Type returnType, YamlSerializerOptions options) - at SharpYaml.YamlSerializer.Deserialize[T](String yaml, YamlSerializerOptions options) - at Elastic.Markdown.Myst.YamlSerialization.Deserialize[T](String yaml, ProductsConfiguration products, Func`2 configure) in {repo}/src/Elastic.Markdown/Myst/YamlSerialization.cs:line 59 - at Elastic.Markdown.Myst.YamlSerialization.Deserialize[T](String yaml, ProductsConfiguration products) in {repo}/src/Elastic.Markdown/Myst/YamlSerialization.cs:line 21 - at Elastic.Markdown.Myst.Directives.AppliesSwitch.AppliesItemBlock.ParseApplicableTo(String yaml) in {repo}/src/Elastic.Markdown/Myst/Directives/AppliesSwitch/AppliesSwitchBlock.cs:line 78 - error {repo}/docs/index.md:5 Unable to parse applies_to definition: stack: [ga - SharpYaml.SemanticErrorException: (Lin: 1, Col: 0, Chr: 10) - (Lin: 1, Col: 0, Chr: 10): While parsing a flow sequence, did not find expected ',' or ']'. - at SharpYaml.Parser`1.ParseFlowSequenceEntry(Boolean isFirst) - at SharpYaml.Parser`1.StateMachine() - at SharpYaml.Parser`1.MoveNext() - at SharpYaml.Serialization.YamlReader.YamlReaderState.ReadCore() - at SharpYaml.Serialization.YamlReader.YamlReaderState.Read() - at SharpYaml.Serialization.YamlReader.Read() - at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) - at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) - at SharpYaml.Serialization.Converters.YamlUntypedObjectConverter.Read(YamlReader reader, Type typeToConvert) - at Elastic.Documentation.AppliesTo.ApplicableToYamlConverter.ReadUntyped(YamlReader reader) in {repo}/src/Elastic.Documentation.Configuration/Converters/ApplicableToYamlConverter.cs:line 41 - at Elastic.Documentation.AppliesTo.ApplicableToYamlConverter.Read(YamlReader reader) in {repo}/src/Elastic.Documentation.Configuration/Converters/ApplicableToYamlConverter.cs:line 94 - at SharpYaml.Serialization.YamlConverter`1.Read(YamlReader reader, Type typeToConvert) - at Elastic.Markdown.Myst.DocsBuilderYamlStaticContext.ReadValue4(YamlReader reader, RuntimeCustomConverterCache runtimeConverters) in {repo}/.artifacts/obj/Elastic.Markdown/release/SharpYaml.SourceGenerator/SharpYaml.SourceGeneration.YamlSerializerContextGenerator/DocsBuilderYamlStaticContext.g.cs:line 2768 - at Elastic.Markdown.Myst.DocsBuilderYamlStaticContext.GeneratedTypeInfo4.Read(YamlReader reader) in {repo}/.artifacts/obj/Elastic.Markdown/release/SharpYaml.SourceGenerator/SharpYaml.SourceGeneration.YamlSerializerContextGenerator/DocsBuilderYamlStaticContext.g.cs:line 485 - at SharpYaml.YamlTypeInfo`1.ReadAsObject(YamlReader reader) - at SharpYaml.YamlSerializer.DeserializeCore(YamlTypeInfo typeInfo, String yaml) - at SharpYaml.YamlSerializer.Deserialize(String yaml, Type returnType, YamlSerializerOptions options) - at SharpYaml.YamlSerializer.Deserialize[T](String yaml, YamlSerializerOptions options) - at Elastic.Markdown.Myst.YamlSerialization.Deserialize[T](String yaml, ProductsConfiguration products, Func`2 configure) in {repo}/src/Elastic.Markdown/Myst/YamlSerialization.cs:line 59 - at Elastic.Markdown.Myst.YamlSerialization.Deserialize[T](String yaml, ProductsConfiguration products) in {repo}/src/Elastic.Markdown/Myst/YamlSerialization.cs:line 21 - at Elastic.Markdown.Myst.Directives.DirectiveHtmlRenderer.ParseApplicableTo(String yaml, DirectiveBlock block) in {repo}/src/Elastic.Markdown/Myst/Directives/DirectiveHtmlRenderer.cs:line 648 - """ + error {repo}/docs/index.md:5 Unable to parse applies_to definition: stack: [ga + error {repo}/docs/index.md:5 Unable to parse applies_to definition: stack: [ga + """ ); [Test] diff --git a/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs b/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs index 19e43d49cd..7c9b987c01 100644 --- a/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/StorybookTests.cs @@ -164,19 +164,12 @@ public class StorybookDisallowedRegistryVariableTests() : StorybookRegistryTest( public void WarnsAndLeavesExpressionLiteral() => Collector .Diagnostics - .ToSnapshotText() + .ToSnapshotText(firstLineOnly: true) .Should() .MatchInlineSnapshot( """ warning {repo}/docs/docset.yml 'storybook.registry' references environment variable 'AWS_SECRET_ACCESS_KEY' which is not allow-listed for interpolation and is left literal. Allowed: KIBANA_STORYBOOK_REGISTRY. error {repo}/docs/index.md:3 storybook registry could not be read: ${AWS_SECRET_ACCESS_KEY:-docs_registry.json} - System.IO.FileNotFoundException: Could not find file '{repo}/docs/${AWS_SECRET_ACCESS_KEY:-docs_registry.json}'. - File name: '{repo}/docs/${AWS_SECRET_ACCESS_KEY:-docs_registry.json}' - at System.IO.Abstractions.TestingHelpers.MockFile.ReadAllText(String path, Encoding encoding) - at System.IO.Abstractions.TestingHelpers.MockFile.ReadAllText(String path) - at Nullean.ScopedFileSystem.ScopedFile.ReadAllText(String path) - at Elastic.Markdown.Myst.Directives.Storybook.StorybookBlock.ReadRegistry(String rawRegistry) in {repo}/src/Elastic.Markdown/Myst/Directives/Storybook/StorybookBlock.cs:line 209 - at Elastic.Markdown.Myst.Directives.Storybook.StorybookBlock.TryReadRegistry(String rawRegistry, String& registryJson, Exception& error) in {repo}/src/Elastic.Markdown/Myst/Directives/Storybook/StorybookBlock.cs:line 184 """ ); } From 2b0c0d96482ca8657090d80cb9c58fe6556356eb Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 5 Nov 2026 14:50:06 +0100 Subject: [PATCH 18/18] Match the repository root in the form DiagnosticsSnapshot writes it DiagnosticsSnapshot drops a leading drive letter and writes forward slashes, so on Windows D:\a\docs-builder\docs-builder\docs\index.md reaches the {repo} scrubber as /a/docs-builder/docs-builder/docs/index.md, which it no longer recognised. Every diagnostics snapshot that names a file failed on the Windows runner. The scrubber now also matches the root without its drive letter. Removing that variant makes the new test fail. Co-Authored-By: Claude Sonnet 5.5 --- .../Linked/SnapshotScrubbing.cs | 7 ++++++- .../Testing/SnapshotHelpersTests.cs | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/tests-shared/Elastic.Documentation.Testing/Linked/SnapshotScrubbing.cs b/tests-shared/Elastic.Documentation.Testing/Linked/SnapshotScrubbing.cs index 7c97d0d9b1..2a0413a362 100644 --- a/tests-shared/Elastic.Documentation.Testing/Linked/SnapshotScrubbing.cs +++ b/tests-shared/Elastic.Documentation.Testing/Linked/SnapshotScrubbing.cs @@ -34,7 +34,10 @@ internal static void Register() internal static Func Under(string root, string token, Regex? randomName = null) { root = root.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); - var roots = new[] { root, root.Replace('\\', '/') }.Distinct().Select(Regex.Escape); + // DiagnosticsSnapshot writes a path with forward slashes and without its drive letter, so the root has to match that way too + var forward = root.Replace('\\', '/'); + var withoutDrive = DriveLetter.Replace(forward, string.Empty); + var roots = new[] { root, forward, withoutDrive }.Where(r => r.Length > 1).Distinct().Select(Regex.Escape); var pattern = new Regex($"(?:{string.Join('|', roots)})(?[^\\s\"'<>:*?|]*)", RegexOptions.Compiled); return text => pattern.Replace(text, m => { @@ -43,6 +46,8 @@ internal static Func Under(string root, string token, Regex? ran }); } + private static readonly Regex DriveLetter = new("^[A-Za-z]:", RegexOptions.Compiled); + /// A run of 32 hex digits: a Guid.ToString("N") used as a folder name. private static readonly Regex RandomName = new("[0-9a-fA-F]{32}", RegexOptions.Compiled); diff --git a/tests/Elastic.Documentation.Tests/Testing/SnapshotHelpersTests.cs b/tests/Elastic.Documentation.Tests/Testing/SnapshotHelpersTests.cs index ca2e42c247..c89933d40b 100644 --- a/tests/Elastic.Documentation.Tests/Testing/SnapshotHelpersTests.cs +++ b/tests/Elastic.Documentation.Tests/Testing/SnapshotHelpersTests.cs @@ -134,4 +134,13 @@ public void ToSnapshotText_FirstLineOnly_DropsTheStackTrace() }; diagnostics.ToSnapshotText(firstLineOnly: true).Should().MatchInlineSnapshot("error index.md:3 registry could not be read"); } + + [Test] + public void PathScrubber_RootAfterDiagnosticsSnapshotNormalisedIt_StillReadsAsRepo() + { + // what a Windows run writes for D:\a\docs-builder\docs-builder\docs\index.md once the drive letter and backslashes are gone + var scrub = SnapshotScrubbing.Under(@"D:\a\docs-builder\docs-builder", "{repo}"); + scrub("error /a/docs-builder/docs-builder/docs/index.md:3 bad").Should().MatchInlineSnapshot("error {repo}/docs/index.md:3 bad"); + scrub(@"error D:\a\docs-builder\docs-builder\docs\index.md:3 bad").Should().MatchInlineSnapshot("error {repo}/docs/index.md:3 bad"); + } }