diff --git a/docs/syntax/changelog.md b/docs/syntax/changelog.md index 3d1ef5997..0fded8deb 100644 --- a/docs/syntax/changelog.md +++ b/docs/syntax/changelog.md @@ -25,6 +25,7 @@ The directive supports the following options: | `:type: value` | Filter entries by type | Excludes separated types | | `:subsections:` | Group entries by area/component | false | | `:link-visibility: value` | Visibility of pull request (PR) and issue links | `auto` | +| `:description-visibility: value` | Visibility of changelog **record** descriptions (YAML `description` on each entry) | `auto` | | `:config: path` | Path to `changelog.yml` configuration | auto-discover | ### Example with options @@ -34,6 +35,7 @@ The directive supports the following options: :type: all :subsections: :link-visibility: keep-links +:description-visibility: keep-descriptions ::: ``` @@ -114,6 +116,18 @@ Bundles whose repo is listed as private in `assembler.yml` hide links by default This aligns with the `changelog render` command's link visibility controls. +#### `:description-visibility:` + +Controls whether the **`description`** text on each **changelog record** appears in output (bullet body text under each item, and the first paragraph inside breaking-change, deprecation, known-issue, and highlight dropdowns). This is **different** from the optional **bundle** `description` field (release intro prose after `_Released:_`), which is always shown when present. See [Rendered output](#rendered-output). + +| Value | Behavior | +|-------|----------| +| `auto` | When **every** constituent repository in the bundle’s resolved repo identity is **public** (same private-repo detection as `:link-visibility:` from `assembler.yml`, including `repo1+repo2` merged bundles), **omit** record `description` bodies. When **any** constituent is marked **private**, **show** those bodies. In standalone builds without `assembler.yml`, every repo is treated as public ⇒ record descriptions are omitted under `auto`. | +| `keep-descriptions` | Always render record descriptions when present in the bundle source. Use this on pages such as deprecations or breaking changes when you still want full release-note prose alongside public repos. | +| `hide-descriptions` | Always omit record `description` bodies (titles, PR/issue links, Impact and Action sections, and bundle-level intros are unaffected). | + +**Contrast with `:link-visibility:`:** `:link-visibility: auto` hides **links** when a repo is **private**. `:description-visibility: auto` **shows** richer record **description** prose when **any** source repo is **private**, and hides that prose for bundles that resolve to **only public** repositories. + #### `:subsections:` When enabled, entries are grouped by "area" within each section. @@ -254,6 +268,8 @@ When present, the `release-date` field is rendered immediately after the version Bundle descriptions are rendered when present in the bundle YAML file. The description appears after the release date (if any) but before any entry sections. Descriptions support Markdown formatting including links, lists, and multiple paragraphs. +**Record descriptions:** Each changelog entry may have its own `description` field in YAML (shown as body text under list items or as the introductory paragraph inside dropdowns). Visibility of **these** descriptions is controlled with `:description-visibility:` (defaults to `auto`; see Option details section). Do not confuse bundle `description` (intro prose) with per-record `description` (entry bodies). + ### Section types | Section | Entry type | Rendering | diff --git a/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogBlock.cs b/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogBlock.cs index 004bd904f..d4218547b 100644 --- a/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogBlock.cs +++ b/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogBlock.cs @@ -160,6 +160,11 @@ public class ChangelogBlock(DirectiveBlockParser parser, ParserContext context) /// public ChangelogLinkVisibility LinkVisibility { get; private set; } + /// + /// Visibility of changelog record description body text (see :description-visibility: option). + /// + public ChangelogDescriptionVisibility DescriptionVisibility { get; private set; } + /// /// Returns all anchors that will be generated by this directive during rendering. /// @@ -183,6 +188,7 @@ public override void FinalizeAndValidate(ParserContext context) LoadConfiguration(); LoadPrivateRepositories(); LinkVisibility = ParseLinkVisibility(); + DescriptionVisibility = ParseDescriptionVisibility(); if (Found) LoadAndCacheBundles(); } @@ -209,6 +215,28 @@ private ChangelogLinkVisibility EmitInvalidLinkVisibilityWarning(string value) return ChangelogLinkVisibility.Auto; } + private ChangelogDescriptionVisibility ParseDescriptionVisibility() + { + var value = Prop("description-visibility"); + if (string.IsNullOrWhiteSpace(value)) + return ChangelogDescriptionVisibility.Auto; + + return value.ToLowerInvariant() switch + { + "auto" => ChangelogDescriptionVisibility.Auto, + "keep-descriptions" => ChangelogDescriptionVisibility.KeepDescriptions, + "hide-descriptions" => ChangelogDescriptionVisibility.HideDescriptions, + _ => EmitInvalidDescriptionVisibilityWarning(value) + }; + } + + private ChangelogDescriptionVisibility EmitInvalidDescriptionVisibilityWarning(string value) + { + this.EmitWarning( + $"Invalid :description-visibility: value '{value}'. Valid values are: auto, keep-descriptions, hide-descriptions. Using auto."); + return ChangelogDescriptionVisibility.Auto; + } + /// /// Parses and validates the :type: option. /// Valid values: all, breaking-change, deprecation, known-issue, highlight. diff --git a/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogDescriptionVisibility.cs b/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogDescriptionVisibility.cs new file mode 100644 index 000000000..3159d4152 --- /dev/null +++ b/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogDescriptionVisibility.cs @@ -0,0 +1,28 @@ +// 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 + +namespace Elastic.Markdown.Myst.Directives.Changelog; + +/// +/// Controls changelog entry description (body text) rendering for the {changelog} directive. +/// Mirrors the structure of while using opposite privacy defaults for . +/// +public enum ChangelogDescriptionVisibility +{ + /// + /// Hide record descriptions when the bundle has only public constituent repos (per assembler.yml); + /// show when any constituent is private. With no private repos configured, hides descriptions everywhere. + /// + Auto, + + /// + /// Always render record descriptions when present in source YAML. + /// + KeepDescriptions, + + /// + /// Never render record descriptions (including dropdown authoring placeholders). + /// + HideDescriptions +} diff --git a/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogInlineRenderer.cs b/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogInlineRenderer.cs index c0c002271..dd975b89c 100644 --- a/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogInlineRenderer.cs +++ b/src/Elastic.Markdown/Myst/Directives/Changelog/ChangelogInlineRenderer.cs @@ -37,7 +37,8 @@ public static class ChangelogInlineRenderer block.PrivateRepositories, block.HideFeatures, typeFilter, - block.LinkVisibility); + block.LinkVisibility, + block.DescriptionVisibility); _ = sb.Append(bundleMarkdown); isFirst = false; @@ -53,7 +54,8 @@ private static string RenderSingleBundle( HashSet privateRepositories, HashSet hideFeatures, ChangelogTypeFilter typeFilter, - ChangelogLinkVisibility linkVisibility) + ChangelogLinkVisibility linkVisibility, + ChangelogDescriptionVisibility descriptionVisibility) { var titleSlug = ChangelogTextUtilities.TitleToSlug(bundle.Version); @@ -78,8 +80,22 @@ private static string RenderSingleBundle( _ => ShouldHideLinksForRepo(bundle.Repo, privateRepositories) }; + var hideEntryDescriptions = ShouldHideEntryDescriptionsForRepo(bundle.Repo, privateRepositories, descriptionVisibility); + var displayVersion = VersionOrDate.FormatDisplayVersion(bundle.Version); - return GenerateMarkdown(displayVersion, titleSlug, bundle.Repo, bundle.Owner, entriesByType, subsections, hideLinks, typeFilter, publishBlocker, bundle.Data?.Description, bundle.Data?.ReleaseDate); + return GenerateMarkdown( + displayVersion, + titleSlug, + bundle.Repo, + bundle.Owner, + entriesByType, + subsections, + hideLinks, + hideEntryDescriptions, + typeFilter, + publishBlocker, + bundle.Data?.Description, + bundle.Data?.ReleaseDate); } /// @@ -123,10 +139,35 @@ public static bool ShouldHideLinksForRepo(string bundleRepo, HashSet pri if (privateRepositories.Count == 0) return false; - // Split on '+' to handle merged bundles (e.g., "elasticsearch+kibana+private-repo") + return HasAnyPrivateRepoConstituent(bundleRepo, privateRepositories); + } + + /// + /// When true, changelog entry YAML description bodies (bullet text and dropdown intro) must not be rendered. + /// + public static bool ShouldHideEntryDescriptionsForRepo( + string bundleRepo, + HashSet privateRepositories, + ChangelogDescriptionVisibility visibility) => + visibility switch + { + ChangelogDescriptionVisibility.HideDescriptions => true, + ChangelogDescriptionVisibility.KeepDescriptions => false, + ChangelogDescriptionVisibility.Auto => !HasAnyPrivateRepoConstituent(bundleRepo, privateRepositories), + _ => !HasAnyPrivateRepoConstituent(bundleRepo, privateRepositories) + }; + + /// + /// True when merged (elasticsearch+kibana-style) has at least one + /// component listed as private for the build. + /// + public static bool HasAnyPrivateRepoConstituent(string bundleRepo, HashSet privateRepositories) + { + if (privateRepositories.Count == 0) + return false; + var repos = bundleRepo.Split('+', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - // Hide links if ANY component repo is private return repos.Any(privateRepositories.Contains); } @@ -151,6 +192,7 @@ private static string GenerateMarkdown( Dictionary> entriesByType, bool subsections, bool hideLinks, + bool hideEntryDescriptions, ChangelogTypeFilter typeFilter, PublishBlocker? publishBlocker, string? description = null, @@ -207,7 +249,7 @@ private static string GenerateMarkdown( if (typeFilter == ChangelogTypeFilter.Highlight) { if (highlights.Count > 0) - RenderDetailedEntries(sb, highlights, repo, owner, groupBySubtype: false, hideLinks, publishBlocker); + RenderDetailedEntries(sb, highlights, repo, owner, groupBySubtype: false, hideLinks, hideEntryDescriptions, publishBlocker); return sb.ToString(); } @@ -215,35 +257,35 @@ private static string GenerateMarkdown( { _ = sb.AppendLine(); _ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Breaking changes [{repo}-{titleSlug}-breaking-changes]"); - RenderDetailedEntries(sb, breakingChanges, repo, owner, groupBySubtype: true, hideLinks, publishBlocker); + RenderDetailedEntries(sb, breakingChanges, repo, owner, groupBySubtype: true, hideLinks, hideEntryDescriptions, publishBlocker); } if (highlights.Count > 0 && typeFilter == ChangelogTypeFilter.All) { _ = sb.AppendLine(); _ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Highlights [{repo}-{titleSlug}-highlights]"); - RenderDetailedEntries(sb, highlights, repo, owner, groupBySubtype: false, hideLinks, publishBlocker); + RenderDetailedEntries(sb, highlights, repo, owner, groupBySubtype: false, hideLinks, hideEntryDescriptions, publishBlocker); } if (security.Count > 0) { _ = sb.AppendLine(); _ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Security [{repo}-{titleSlug}-security]"); - RenderEntriesByArea(sb, security, repo, owner, subsections, hideLinks, publishBlocker); + RenderEntriesByArea(sb, security, repo, owner, subsections, hideLinks, hideEntryDescriptions, publishBlocker); } if (knownIssues.Count > 0) { _ = sb.AppendLine(); _ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Known issues [{repo}-{titleSlug}-known-issues]"); - RenderDetailedEntries(sb, knownIssues, repo, owner, groupBySubtype: false, hideLinks, publishBlocker); + RenderDetailedEntries(sb, knownIssues, repo, owner, groupBySubtype: false, hideLinks, hideEntryDescriptions, publishBlocker); } if (deprecations.Count > 0) { _ = sb.AppendLine(); _ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Deprecations [{repo}-{titleSlug}-deprecations]"); - RenderDetailedEntries(sb, deprecations, repo, owner, groupBySubtype: false, hideLinks, publishBlocker); + RenderDetailedEntries(sb, deprecations, repo, owner, groupBySubtype: false, hideLinks, hideEntryDescriptions, publishBlocker); } if (features.Count > 0 || enhancements.Count > 0) @@ -251,35 +293,35 @@ private static string GenerateMarkdown( _ = sb.AppendLine(); _ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Features and enhancements [{repo}-{titleSlug}-features-enhancements]"); var combined = features.Concat(enhancements).ToList(); - RenderEntriesByArea(sb, combined, repo, owner, subsections, hideLinks, publishBlocker); + RenderEntriesByArea(sb, combined, repo, owner, subsections, hideLinks, hideEntryDescriptions, publishBlocker); } if (bugFixes.Count > 0) { _ = sb.AppendLine(); _ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Fixes [{repo}-{titleSlug}-fixes]"); - RenderEntriesByArea(sb, bugFixes, repo, owner, subsections, hideLinks, publishBlocker); + RenderEntriesByArea(sb, bugFixes, repo, owner, subsections, hideLinks, hideEntryDescriptions, publishBlocker); } if (docs.Count > 0) { _ = sb.AppendLine(); _ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Documentation [{repo}-{titleSlug}-docs]"); - RenderEntriesByArea(sb, docs, repo, owner, subsections, hideLinks, publishBlocker); + RenderEntriesByArea(sb, docs, repo, owner, subsections, hideLinks, hideEntryDescriptions, publishBlocker); } if (regressions.Count > 0) { _ = sb.AppendLine(); _ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Regressions [{repo}-{titleSlug}-regressions]"); - RenderEntriesByArea(sb, regressions, repo, owner, subsections, hideLinks, publishBlocker); + RenderEntriesByArea(sb, regressions, repo, owner, subsections, hideLinks, hideEntryDescriptions, publishBlocker); } if (other.Count > 0) { _ = sb.AppendLine(); _ = sb.AppendLine(CultureInfo.InvariantCulture, $"### Other changes [{repo}-{titleSlug}-other]"); - RenderEntriesByArea(sb, other, repo, owner, subsections, hideLinks, publishBlocker); + RenderEntriesByArea(sb, other, repo, owner, subsections, hideLinks, hideEntryDescriptions, publishBlocker); } return sb.ToString(); @@ -292,6 +334,7 @@ private static void RenderEntriesByArea( string owner, bool subsections, bool hideLinks, + bool hideEntryDescriptions, PublishBlocker? publishBlocker) { if (subsections) @@ -309,29 +352,29 @@ private static void RenderEntriesByArea( } foreach (var entry in areaGroup) - RenderSingleEntry(sb, entry, repo, owner, hideLinks); + RenderSingleEntry(sb, entry, repo, owner, hideLinks, hideEntryDescriptions); } } else { foreach (var entry in entries) - RenderSingleEntry(sb, entry, repo, owner, hideLinks); + RenderSingleEntry(sb, entry, repo, owner, hideLinks, hideEntryDescriptions); } } - private static void RenderSingleEntry(StringBuilder sb, ChangelogEntry entry, string repo, string owner, bool hideLinks) + private static void RenderSingleEntry(StringBuilder sb, ChangelogEntry entry, string repo, string owner, bool hideLinks, bool hideEntryDescriptions) { _ = sb.Append("* "); _ = sb.Append(ChangelogTextUtilities.Beautify(entry.Title)); RenderEntryLinks(sb, entry, repo, owner, hideLinks); - if (!string.IsNullOrWhiteSpace(entry.Description)) - { - _ = sb.AppendLine(); - var indented = ChangelogTextUtilities.Indent(entry.Description); - _ = sb.AppendLine(indented); - } + if (hideEntryDescriptions || string.IsNullOrWhiteSpace(entry.Description)) + return; + + _ = sb.AppendLine(); + var indented = ChangelogTextUtilities.Indent(entry.Description); + _ = sb.AppendLine(indented); } private static void RenderEntryLinks(StringBuilder sb, ChangelogEntry entry, string repo, string owner, bool hideLinks) @@ -373,6 +416,7 @@ private static void RenderDetailedEntries( string owner, bool groupBySubtype, bool hideLinks, + bool hideEntryDescriptions, PublishBlocker? publishBlocker) { var grouped = groupBySubtype @@ -391,16 +435,21 @@ private static void RenderDetailedEntries( } foreach (var entry in group) - RenderDetailedEntry(sb, entry, repo, owner, hideLinks); + RenderDetailedEntry(sb, entry, repo, owner, hideLinks, hideEntryDescriptions); } } - private static void RenderDetailedEntry(StringBuilder sb, ChangelogEntry entry, string repo, string owner, bool hideLinks) + private static void RenderDetailedEntry(StringBuilder sb, ChangelogEntry entry, string repo, string owner, bool hideLinks, bool hideEntryDescriptions) { _ = sb.AppendLine(); _ = sb.AppendLine(CultureInfo.InvariantCulture, $"::::{{dropdown}} {ChangelogTextUtilities.Beautify(entry.Title)}"); - _ = sb.AppendLine(entry.Description ?? "% Describe the change"); - _ = sb.AppendLine(); + if (!hideEntryDescriptions) + { + _ = sb.AppendLine(entry.Description ?? "% Describe the change"); + _ = sb.AppendLine(); + } + else + _ = sb.AppendLine(); RenderDetailedEntryLinks(sb, entry, repo, owner, hideLinks); diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs index b15f82abf..428597ee3 100644 --- a/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogBasicTests.cs @@ -582,6 +582,7 @@ public ChangelogTitleDescriptionSpacingTests(ITestOutputHelper output) : base(ou // language=markdown """ :::{changelog} + :description-visibility: keep-descriptions ::: """) => FileSystem.AddFile("docs/changelog/bundles/9.3.0.yaml", new MockFileData( // language=yaml diff --git a/tests/Elastic.Markdown.Tests/Directives/ChangelogDescriptionVisibilityTests.cs b/tests/Elastic.Markdown.Tests/Directives/ChangelogDescriptionVisibilityTests.cs new file mode 100644 index 000000000..956e8b420 --- /dev/null +++ b/tests/Elastic.Markdown.Tests/Directives/ChangelogDescriptionVisibilityTests.cs @@ -0,0 +1,277 @@ +// 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.Markdown.Myst.Directives.Changelog; + +namespace Elastic.Markdown.Tests.Directives; + +/// Unit tests for . +public class ChangelogShouldHideEntryDescriptionsTests +{ + [Fact] + public void HideDescriptions_AlwaysReturnsTrue() + { + var privateRepos = new HashSet(StringComparer.OrdinalIgnoreCase) { "x" }; + + var result = ChangelogInlineRenderer.ShouldHideEntryDescriptionsForRepo( + "kibana", + privateRepos, + ChangelogDescriptionVisibility.HideDescriptions); + + result.Should().BeTrue(); + } + + [Fact] + public void KeepDescriptions_AlwaysReturnsFalse() + { + var result = ChangelogInlineRenderer.ShouldHideEntryDescriptionsForRepo( + "kibana", + [], + ChangelogDescriptionVisibility.KeepDescriptions); + + result.Should().BeFalse(); + } + + [Fact] + public void Auto_WithEmptyPrivateRepos_HidesBodies() + { + var result = ChangelogInlineRenderer.ShouldHideEntryDescriptionsForRepo( + "kibana", + [], + ChangelogDescriptionVisibility.Auto); + + result.Should().BeTrue(); + } + + [Fact] + public void Auto_WithPublicRepoOnly_HidesBodies() + { + var privateRepos = new HashSet(StringComparer.OrdinalIgnoreCase) { "secret-repo" }; + + var result = ChangelogInlineRenderer.ShouldHideEntryDescriptionsForRepo( + "kibana", + privateRepos, + ChangelogDescriptionVisibility.Auto); + + result.Should().BeTrue(); + } + + [Fact] + public void Auto_WithPrivateRepo_ShowsBodies() + { + var privateRepos = new HashSet(StringComparer.OrdinalIgnoreCase) { "kibana" }; + + var result = ChangelogInlineRenderer.ShouldHideEntryDescriptionsForRepo( + "kibana", + privateRepos, + ChangelogDescriptionVisibility.Auto); + + result.Should().BeFalse(); + } + + [Fact] + public void Auto_WithMergedBundle_OnePrivateConstituent_ShowsBodies() + { + var privateRepos = new HashSet(StringComparer.OrdinalIgnoreCase) { "kibana" }; + + var result = ChangelogInlineRenderer.ShouldHideEntryDescriptionsForRepo( + "elasticsearch+kibana", + privateRepos, + ChangelogDescriptionVisibility.Auto); + + result.Should().BeFalse(); + } + + [Fact] + public void Auto_WithMergedBundle_AllPublicConstituents_HidesBodies() + { + var privateRepos = new HashSet(StringComparer.OrdinalIgnoreCase) { "other-private" }; + + var result = ChangelogInlineRenderer.ShouldHideEntryDescriptionsForRepo( + "elasticsearch+kibana", + privateRepos, + ChangelogDescriptionVisibility.Auto); + + result.Should().BeTrue(); + } +} + +/// +/// Omitting :description-visibility: defaults to . +/// +public class ChangelogDescriptionVisibilityDefaultTests(ITestOutputHelper output) : DirectiveTest(output, + """ + :::{changelog} + ::: + """) +{ + protected override void AddToFileSystem(MockFileSystem fileSystem) => + fileSystem.AddFile("docs/changelog/bundles/9.3.0.yaml", new MockFileData( + """ + products: + - product: elasticsearch + target: 9.3.0 + entries: + - title: Feature delta + type: feature + products: + - product: elasticsearch + target: 9.3.0 + description: BODY_DEFAULT_AUTO_VISIBILITY + """)); + + [Fact] + public void PropertyDefaultsToAuto() => + Block!.DescriptionVisibility.Should().Be(ChangelogDescriptionVisibility.Auto); + + /// Public bundle with no assembler private repos ⇒ auto hides record bodies. + [Fact] + public void HtmlOmitsBodyTextForPublicBundle() => + Html.Should().NotContain("BODY_DEFAULT_AUTO_VISIBILITY"); + + [Fact] + public void HtmlStillRendersTitles() => + Html.Should().Contain("Feature delta"); +} + +public class ChangelogDescriptionVisibilityAutoShowsForPrivateRepoTests(ITestOutputHelper output) : DirectiveTest(output, + """ + :::{changelog} + ::: + """) +{ + protected override void AddToFileSystem(MockFileSystem fileSystem) => + fileSystem.AddFile("docs/changelog/bundles/9.3.0.yaml", new MockFileData( + """ + products: + - product: elasticsearch + target: 9.3.0 + entries: + - title: Feature epsilon + type: feature + products: + - product: elasticsearch + target: 9.3.0 + description: BODY_PRIVATE_VISIBILITY_TEST + """)); + + public override async ValueTask InitializeAsync() + { + await base.InitializeAsync(); + _ = Block!.PrivateRepositories.Add("elasticsearch"); + } + + [Fact] + public void MarkdownIncludesBodyTextWhenRepoIsPrivateForAutoMode() + { + var markdown = ChangelogInlineRenderer.RenderChangelogMarkdown(Block!); + markdown.Should().Contain("BODY_PRIVATE_VISIBILITY_TEST"); + } + + [Fact] + public void MarkdownRendersTitle() + { + var markdown = ChangelogInlineRenderer.RenderChangelogMarkdown(Block!); + markdown.Should().Contain("Feature epsilon"); + } +} + +public class ChangelogDescriptionVisibilityKeepExplicitTests(ITestOutputHelper output) : DirectiveTest(output, + """ + :::{changelog} + :description-visibility: keep-descriptions + ::: + """) +{ + protected override void AddToFileSystem(MockFileSystem fileSystem) => + fileSystem.AddFile("docs/changelog/bundles/9.3.0.yaml", new MockFileData( + """ + products: + - product: elasticsearch + target: 9.3.0 + entries: + - title: Feature keep + type: feature + products: + - product: elasticsearch + target: 9.3.0 + description: BODY_KEEP_VISIBILITY + """)); + + [Fact] + public void KeepsBodyOnFullyPublicRepos() => + Html.Should().Contain("BODY_KEEP_VISIBILITY"); +} + +public class ChangelogDescriptionVisibilityHideExplicitTests(ITestOutputHelper output) : DirectiveTest(output, + """ + :::{changelog} + :description-visibility: hide-descriptions + ::: + """) +{ + protected override void AddToFileSystem(MockFileSystem fileSystem) => + fileSystem.AddFile("docs/changelog/bundles/9.3.0.yaml", new MockFileData( + """ + products: + - product: elasticsearch + target: 9.3.0 + entries: + - title: Feature hide + type: feature + products: + - product: elasticsearch + target: 9.3.0 + description: BODY_HIDE_VISIBILITY + """)); + + [Fact] + public void OmitBody() => + Html.Should().NotContain("BODY_HIDE_VISIBILITY"); + + [Fact] + public void MarkdownRendersTitlesWithoutBodies() + { + var markdown = ChangelogInlineRenderer.RenderChangelogMarkdown(Block!); + markdown.Should().Contain("Feature hide"); + markdown.Should().NotContain("BODY_HIDE_VISIBILITY"); + } +} + +public class ChangelogDescriptionVisibilityInvalidTests(ITestOutputHelper output) : DirectiveTest(output, + """ + :::{changelog} + :description-visibility: nonsense-value + ::: + """) +{ + protected override void AddToFileSystem(MockFileSystem fileSystem) => + fileSystem.AddFile("docs/changelog/bundles/9.3.0.yaml", new MockFileData( + """ + products: + - product: elasticsearch + target: 9.3.0 + entries: + - title: Feature warn + type: feature + products: + - product: elasticsearch + target: 9.3.0 + description: BODY_INVALID_VISIBILITY + """)); + + [Fact] + public void FallsBackToAuto() => + Block!.DescriptionVisibility.Should().Be(ChangelogDescriptionVisibility.Auto); + + [Fact] + public void EmitsWarning() => + Collector.Warnings.Should().BeGreaterThan(0); + + [Fact] + public void AutoTreatsFullyPublic_AsHideBody() => + Html.Should().NotContain("BODY_INVALID_VISIBILITY"); +}