From a9c4a6098c9b8d6033227b7af85ccb30424345a3 Mon Sep 17 00:00:00 2001 From: James Gunn Date: Wed, 25 Dec 2024 17:44:05 +0000 Subject: [PATCH] Port Breadcrumbs to IComponentGenerator --- docs/components/breadcrumbs.md | 14 ++-- .../Pages/Breadcrumbs/Breadcrumbs.cshtml | 6 +- .../ComponentGeneration/BreadcrumbsOptions.cs | 28 ++++++- .../DefaultComponentGenerator.Breadcrumbs.cs | 48 ++++++++++++ .../IComponentGenerator.cs | 6 ++ .../HtmlGeneration/BreadcrumbsItem.cs | 12 --- .../ComponentGenerator.Breadcrumbs.cs | 76 ------------------- .../IGovUkHtmlGenerator.cs | 5 -- .../TagHelpers/BreadcrumbsContext.cs | 13 ++-- .../TagHelpers/BreadcrumbsItemTagHelper.cs | 22 +++--- .../TagHelpers/BreadcrumbsTagHelper.cs | 45 +++++------ .../ComponentTests.Breadcrumbs.cs | 33 -------- .../ComponentGeneration/ComponentTests.cs | 7 ++ .../BreadcrumbsItemTagHelperTests.cs | 17 +++-- .../TagHelpers/BreadcrumbsTagHelperTests.cs | 63 ++++++++------- 15 files changed, 183 insertions(+), 212 deletions(-) create mode 100644 src/GovUk.Frontend.AspNetCore/ComponentGeneration/DefaultComponentGenerator.Breadcrumbs.cs delete mode 100644 src/GovUk.Frontend.AspNetCore/HtmlGeneration/BreadcrumbsItem.cs delete mode 100644 src/GovUk.Frontend.AspNetCore/HtmlGeneration/ComponentGenerator.Breadcrumbs.cs delete mode 100644 tests/GovUk.Frontend.AspNetCore.ConformanceTests/ComponentTests.Breadcrumbs.cs diff --git a/docs/components/breadcrumbs.md b/docs/components/breadcrumbs.md index 1759fc1a..14b68ccc 100644 --- a/docs/components/breadcrumbs.md +++ b/docs/components/breadcrumbs.md @@ -6,9 +6,9 @@ ```razor - Home - Passports, travel and living abroad - Travel abroad + Home + Passports, travel and living abroad + Travel abroad ``` @@ -18,11 +18,11 @@ ### `` -| Attribute | Type | Description | -| --- | --- | --- | -| `collapse-on-mobile` | `bool` | When true, the breadcrumbs will collapse to the first and last item only on tablet breakpoint and below. Default is `false`. | +| Attribute | Type | Description | +| --- |---------|--------------------------------------------------------------------------------------------------------------------------------------------------| +| `collapse-on-mobile` | `bool?` | When true, the breadcrumbs will collapse to the first and last item only on tablet breakpoint and below. If not specified, `false` will be used. | -### `` +### `` Content is the HTML to use within the breadcrumbs item.\ Must be inside a `` element. diff --git a/src/GovUk.Frontend.AspNetCore.DocSamples/Pages/Breadcrumbs/Breadcrumbs.cshtml b/src/GovUk.Frontend.AspNetCore.DocSamples/Pages/Breadcrumbs/Breadcrumbs.cshtml index a9862dd5..72b9fc71 100644 --- a/src/GovUk.Frontend.AspNetCore.DocSamples/Pages/Breadcrumbs/Breadcrumbs.cshtml +++ b/src/GovUk.Frontend.AspNetCore.DocSamples/Pages/Breadcrumbs/Breadcrumbs.cshtml @@ -1,7 +1,7 @@ @page - Home - Passports, travel and living abroad - Travel abroad + Home + Passports, travel and living abroad + Travel abroad diff --git a/src/GovUk.Frontend.AspNetCore/ComponentGeneration/BreadcrumbsOptions.cs b/src/GovUk.Frontend.AspNetCore/ComponentGeneration/BreadcrumbsOptions.cs index 9619432f..db81cc62 100644 --- a/src/GovUk.Frontend.AspNetCore/ComponentGeneration/BreadcrumbsOptions.cs +++ b/src/GovUk.Frontend.AspNetCore/ComponentGeneration/BreadcrumbsOptions.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Microsoft.AspNetCore.Html; @@ -8,9 +9,23 @@ namespace GovUk.Frontend.AspNetCore.ComponentGeneration; public class BreadcrumbsOptions { public bool? CollapseOnMobile { get; set; } - public string? Classes { get; set; } + public IHtmlContent? Classes { get; set; } public EncodedAttributesDictionary? Attributes { get; set; } public IReadOnlyCollection? Items { get; set; } + + internal void Validate() + { + if (Items is null) + { + throw new InvalidOptionsException(GetType(), $"{nameof(Items)} must be specified."); + } + + int i = 0; + foreach (var item in Items) + { + item.Validate(i++); + } + } } public class BreadcrumbsOptionsItem @@ -19,4 +34,15 @@ public class BreadcrumbsOptionsItem public string? Text { get; set; } public IHtmlContent? Href { get; set; } public EncodedAttributesDictionary? Attributes { get; set; } + + [NonStandardParameter] + internal EncodedAttributesDictionary? ItemAttributes { get; set; } + + internal void Validate(int itemIndex) + { + if (Html.NormalizeEmptyString() is null && Text.NormalizeEmptyString() is null) + { + throw new InvalidOptionsException(GetType(), $"{nameof(Html)} or {nameof(Text)} must be specified on item {itemIndex}."); + } + } } diff --git a/src/GovUk.Frontend.AspNetCore/ComponentGeneration/DefaultComponentGenerator.Breadcrumbs.cs b/src/GovUk.Frontend.AspNetCore/ComponentGeneration/DefaultComponentGenerator.Breadcrumbs.cs new file mode 100644 index 00000000..a77048a8 --- /dev/null +++ b/src/GovUk.Frontend.AspNetCore/ComponentGeneration/DefaultComponentGenerator.Breadcrumbs.cs @@ -0,0 +1,48 @@ +using System; +using System.Linq; + +namespace GovUk.Frontend.AspNetCore.ComponentGeneration; + +public partial class DefaultComponentGenerator +{ + internal const string BreadcrumbsElement = "div"; + internal const bool BreadcrumbsDefaultCollapseOnMobile = false; + internal const string BreadcrumbsItemElement = "li"; + + /// + public virtual HtmlTagBuilder GenerateBreadcrumbs(BreadcrumbsOptions options) + { + ArgumentNullException.ThrowIfNull(options); + options.Validate(); + + return new HtmlTagBuilder(BreadcrumbsElement) + .WithCssClass("govuk-breadcrumbs") + .WithCssClasses(ExplodeClasses(options.Classes?.ToHtmlString())) + .When(options.CollapseOnMobile == true, b => b.WithCssClass("govuk-breadcrumbs--collapse-on-mobile")) + .WithAttributes(options.Attributes) + .WithAppendedHtml(new HtmlTagBuilder("ol") + .WithCssClass("govuk-breadcrumbs__list") + .WithAppendedHtml(options.Items!.Select(item => + { + var content = GetEncodedTextOrHtml(item.Text, item.Html)!; + var gotLink = item.Href.NormalizeEmptyString() is not null; + + return new HtmlTagBuilder(BreadcrumbsItemElement) + .WithCssClass("govuk-breadcrumbs__list-item") + .WithAttributes(item.ItemAttributes) + .When( + !gotLink, + b => b + .WithAttribute("aria-current", "page", encodeValue: false) + .WithAppendedHtml(content)) + .When( + gotLink, + b => b + .WithAppendedHtml(new HtmlTagBuilder("a") + .WithCssClass("govuk-breadcrumbs__link") + .WithAttribute("href", item.Href!) + .WithAttributes(item.Attributes) + .WithAppendedHtml(content))); + }))); + } +} diff --git a/src/GovUk.Frontend.AspNetCore/ComponentGeneration/IComponentGenerator.cs b/src/GovUk.Frontend.AspNetCore/ComponentGeneration/IComponentGenerator.cs index cc1f6844..6da21251 100644 --- a/src/GovUk.Frontend.AspNetCore/ComponentGeneration/IComponentGenerator.cs +++ b/src/GovUk.Frontend.AspNetCore/ComponentGeneration/IComponentGenerator.cs @@ -17,6 +17,12 @@ public interface IComponentGenerator /// An with the component's HTML. HtmlTagBuilder GenerateBackLink(BackLinkOptions options); + /// + /// Generates a breadcrumbs component. + /// + /// An with the component's HTML. + HtmlTagBuilder GenerateBreadcrumbs(BreadcrumbsOptions options); + /// /// Generates a button component. /// diff --git a/src/GovUk.Frontend.AspNetCore/HtmlGeneration/BreadcrumbsItem.cs b/src/GovUk.Frontend.AspNetCore/HtmlGeneration/BreadcrumbsItem.cs deleted file mode 100644 index cb83a100..00000000 --- a/src/GovUk.Frontend.AspNetCore/HtmlGeneration/BreadcrumbsItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.AspNetCore.Html; -using Microsoft.AspNetCore.Mvc.ViewFeatures; - -namespace GovUk.Frontend.AspNetCore.HtmlGeneration; - -internal class BreadcrumbsItem -{ - public string? Href { get; set; } - public AttributeDictionary? LinkAttributes { get; set; } - public IHtmlContent? Content { get; set; } - public AttributeDictionary? Attributes { get; set; } -} diff --git a/src/GovUk.Frontend.AspNetCore/HtmlGeneration/ComponentGenerator.Breadcrumbs.cs b/src/GovUk.Frontend.AspNetCore/HtmlGeneration/ComponentGenerator.Breadcrumbs.cs deleted file mode 100644 index 99970028..00000000 --- a/src/GovUk.Frontend.AspNetCore/HtmlGeneration/ComponentGenerator.Breadcrumbs.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Html; -using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.ViewFeatures; - -namespace GovUk.Frontend.AspNetCore.HtmlGeneration; - -internal partial class ComponentGenerator -{ - internal const string BreadcrumbsElement = "div"; - internal const bool BreadcrumbsDefaultCollapseOnMobile = false; - internal const string BreadcrumbsItemElement = "li"; - - public TagBuilder GenerateBreadcrumbs( - bool collapseOnMobile, - AttributeDictionary? attributes, - IEnumerable items) - { - Guard.ArgumentNotNull(nameof(items), items); - - var tagBuilder = new TagBuilder(BreadcrumbsElement); - tagBuilder.MergeOptionalAttributes(attributes); - tagBuilder.MergeCssClass("govuk-breadcrumbs"); - - if (collapseOnMobile) - { - tagBuilder.MergeCssClass("govuk-breadcrumbs--collapse-on-mobile"); - } - - var ol = new TagBuilder("ol"); - ol.MergeCssClass("govuk-breadcrumbs__list"); - - var index = 0; - foreach (var item in items) - { - if (item.Content == null) - { - throw new ArgumentException( - $"Item {index} is not valid; {nameof(BreadcrumbsItem.Content)} cannot be null.", - nameof(items)); - } - - var li = new TagBuilder(BreadcrumbsItemElement); - li.MergeOptionalAttributes(item.Attributes); - li.MergeCssClass("govuk-breadcrumbs__list-item"); - - IHtmlContent itemContent; - - if (item.Href != null) - { - var itemLink = new TagBuilder("a"); - itemLink.MergeOptionalAttributes(item.LinkAttributes); - itemLink.MergeCssClass("govuk-breadcrumbs__link"); - itemLink.Attributes.Add("href", item.Href); - itemLink.InnerHtml.AppendHtml(item.Content); - itemContent = itemLink; - } - else - { - li.Attributes.Add("aria-current", "page"); - itemContent = item.Content; - } - - li.InnerHtml.AppendHtml(itemContent); - - ol.InnerHtml.AppendHtml(li); - - index++; - } - - tagBuilder.InnerHtml.AppendHtml(ol); - - return tagBuilder; - } -} diff --git a/src/GovUk.Frontend.AspNetCore/IGovUkHtmlGenerator.cs b/src/GovUk.Frontend.AspNetCore/IGovUkHtmlGenerator.cs index 15f769f5..cf682e22 100644 --- a/src/GovUk.Frontend.AspNetCore/IGovUkHtmlGenerator.cs +++ b/src/GovUk.Frontend.AspNetCore/IGovUkHtmlGenerator.cs @@ -22,11 +22,6 @@ TagBuilder GenerateAccordion( string showSectionAriaLabelText, IEnumerable items); - TagBuilder GenerateBreadcrumbs( - bool collapseOnMobile, - AttributeDictionary attributes, - IEnumerable items); - TagBuilder GenerateCharacterCount( string textAreaId, int? maxLength, diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/BreadcrumbsContext.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/BreadcrumbsContext.cs index 39ab0102..ab8e8479 100644 --- a/src/GovUk.Frontend.AspNetCore/TagHelpers/BreadcrumbsContext.cs +++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/BreadcrumbsContext.cs @@ -1,22 +1,23 @@ +using System; using System.Collections.Generic; -using GovUk.Frontend.AspNetCore.HtmlGeneration; +using GovUk.Frontend.AspNetCore.ComponentGeneration; namespace GovUk.Frontend.AspNetCore.TagHelpers; internal class BreadcrumbsContext { - private readonly List _items; + private readonly List _items; public BreadcrumbsContext() { - _items = new List(); + _items = new List(); } - public IReadOnlyCollection Items => _items; + public IReadOnlyCollection Items => _items; - public void AddItem(BreadcrumbsItem item) + public void AddItem(BreadcrumbsOptionsItem item) { - Guard.ArgumentNotNull(nameof(item), item); + ArgumentNullException.ThrowIfNull(item); _items.Add(item); } diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/BreadcrumbsItemTagHelper.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/BreadcrumbsItemTagHelper.cs index 6cb26ae1..785c0bf6 100644 --- a/src/GovUk.Frontend.AspNetCore/TagHelpers/BreadcrumbsItemTagHelper.cs +++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/BreadcrumbsItemTagHelper.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; +using GovUk.Frontend.AspNetCore.ComponentGeneration; using GovUk.Frontend.AspNetCore.HtmlGeneration; using Microsoft.AspNetCore.Razor.TagHelpers; @@ -9,10 +10,12 @@ namespace GovUk.Frontend.AspNetCore.TagHelpers; /// Represents an item in a GDS breadcrumbs component. /// [HtmlTargetElement(TagName, ParentTag = BreadcrumbsTagHelper.TagName)] +[HtmlTargetElement(ShortTagName, ParentTag = BreadcrumbsTagHelper.TagName)] //[OutputElementHint(ComponentGenerator.BreadcrumbsItemElement)] // Omitted since it produces intellisense warnings public class BreadcrumbsItemTagHelper : TagHelper { internal const string TagName = "govuk-breadcrumbs-item"; + internal const string ShortTagName = ShortTagNames.Item; private const string LinkAttributesPrefix = "link-"; @@ -27,27 +30,22 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu { var breadcrumbsContext = context.GetContextItem(); - var childContent = await output.GetChildContentAsync(); + var childContent = (await output.GetChildContentAsync()).Snapshot(); if (output.Content.IsModified) { childContent = output.Content; } - string? href = null; + var attributes = new EncodedAttributesDictionary(output.Attributes); + attributes.Remove("href", out var href); - if (output.Attributes.TryGetAttribute("href", out var hrefAttribute)) + breadcrumbsContext.AddItem(new BreadcrumbsOptionsItem() { - href = hrefAttribute.Value.ToString(); - output.Attributes.Remove(hrefAttribute); - } - - breadcrumbsContext.AddItem(new BreadcrumbsItem() - { - Attributes = output.Attributes.ToAttributeDictionary(), + ItemAttributes = attributes, Href = href, - LinkAttributes = LinkAttributes.ToAttributeDictionary(), - Content = childContent.Snapshot() + Attributes = EncodedAttributesDictionary.FromDictionaryWithEncodedValues(LinkAttributes), + Html = childContent }); output.SuppressOutput(); diff --git a/src/GovUk.Frontend.AspNetCore/TagHelpers/BreadcrumbsTagHelper.cs b/src/GovUk.Frontend.AspNetCore/TagHelpers/BreadcrumbsTagHelper.cs index ff4a2c12..7fad1d08 100644 --- a/src/GovUk.Frontend.AspNetCore/TagHelpers/BreadcrumbsTagHelper.cs +++ b/src/GovUk.Frontend.AspNetCore/TagHelpers/BreadcrumbsTagHelper.cs @@ -1,6 +1,6 @@ +using System; using System.Threading.Tasks; -using GovUk.Frontend.AspNetCore.HtmlGeneration; -using Microsoft.AspNetCore.Mvc.TagHelpers; +using GovUk.Frontend.AspNetCore.ComponentGeneration; using Microsoft.AspNetCore.Razor.TagHelpers; namespace GovUk.Frontend.AspNetCore.TagHelpers; @@ -9,37 +9,33 @@ namespace GovUk.Frontend.AspNetCore.TagHelpers; /// Generates a GDS breadcrumbs component. /// [HtmlTargetElement(TagName)] -[RestrictChildren(BreadcrumbsItemTagHelper.TagName)] -[OutputElementHint(ComponentGenerator.BreadcrumbsElement)] +[RestrictChildren(BreadcrumbsItemTagHelper.TagName, BreadcrumbsItemTagHelper.ShortTagName)] +[OutputElementHint(DefaultComponentGenerator.BreadcrumbsElement)] public class BreadcrumbsTagHelper : TagHelper { internal const string TagName = "govuk-breadcrumbs"; private const string CollapseOnMobileAttributeName = "collapse-on-mobile"; - private readonly IGovUkHtmlGenerator _htmlGenerator; + private readonly IComponentGenerator _componentGenerator; /// - /// Creates a new . + /// Creates a new . /// - public BreadcrumbsTagHelper() - : this(null) + public BreadcrumbsTagHelper(IComponentGenerator componentGenerator) { - } - - internal BreadcrumbsTagHelper(IGovUkHtmlGenerator? htmlGenerator = null) - { - _htmlGenerator = htmlGenerator ?? new ComponentGenerator(); + ArgumentNullException.ThrowIfNull(componentGenerator); + _componentGenerator = componentGenerator; } /// /// Whether to collapse to the first and last item only on tablet breakpoint and below. /// /// - /// The default is . + /// If not specified, will be used. /// [HtmlAttributeName(CollapseOnMobileAttributeName)] - public bool CollapseOnMobile { get; set; } = ComponentGenerator.BreadcrumbsDefaultCollapseOnMobile; + public bool? CollapseOnMobile { get; set; } /// public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) @@ -51,16 +47,17 @@ public override async Task ProcessAsync(TagHelperContext context, TagHelperOutpu await output.GetChildContentAsync(); } - var tagBuilder = _htmlGenerator.GenerateBreadcrumbs( - CollapseOnMobile, - output.Attributes.ToAttributeDictionary(), - breadcrumbsContext.Items); + var attributes = new EncodedAttributesDictionary(output.Attributes); + attributes.Remove("class", out var classes); - output.TagName = tagBuilder.TagName; - output.TagMode = TagMode.StartTagAndEndTag; + var component = _componentGenerator.GenerateBreadcrumbs(new BreadcrumbsOptions + { + CollapseOnMobile = CollapseOnMobile, + Classes = classes, + Attributes = attributes, + Items = breadcrumbsContext.Items + }); - output.Attributes.Clear(); - output.MergeAttributes(tagBuilder); - output.Content.SetHtmlContent(tagBuilder.InnerHtml); + component.WriteTo(output); } } diff --git a/tests/GovUk.Frontend.AspNetCore.ConformanceTests/ComponentTests.Breadcrumbs.cs b/tests/GovUk.Frontend.AspNetCore.ConformanceTests/ComponentTests.Breadcrumbs.cs deleted file mode 100644 index 2578d3fe..00000000 --- a/tests/GovUk.Frontend.AspNetCore.ConformanceTests/ComponentTests.Breadcrumbs.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Linq; -using GovUk.Frontend.AspNetCore.HtmlGeneration; -using GovUk.Frontend.AspNetCore.TestCommon; -using Xunit; - -namespace GovUk.Frontend.AspNetCore.ConformanceTests; - -public partial class ComponentTests -{ - [Theory] - [ComponentFixtureData("breadcrumbs", typeof(OptionsJson.Breadcrumbs))] - public void Breadcrumbs(ComponentTestCaseData data) => - CheckComponentHtmlMatchesExpectedHtml( - data, - (generator, options) => - { - var collapseOnMobile = options.CollapseOnMobile ?? ComponentGenerator.BreadcrumbsDefaultCollapseOnMobile; - - var attributes = options.Attributes.ToAttributesDictionary() - .MergeAttribute("class", options.Classes); - - var items = options.Items - .Select(i => new BreadcrumbsItem() - { - Content = TextOrHtmlHelper.GetHtmlContent(i.Text, i.Html), - Href = i.Href, - LinkAttributes = i.Attributes.ToAttributesDictionary() - }); - - return generator.GenerateBreadcrumbs(collapseOnMobile, attributes, items) - .ToHtmlString(); - }); -} diff --git a/tests/GovUk.Frontend.AspNetCore.Tests/ComponentGeneration/ComponentTests.cs b/tests/GovUk.Frontend.AspNetCore.Tests/ComponentGeneration/ComponentTests.cs index f92f1b52..5a056346 100644 --- a/tests/GovUk.Frontend.AspNetCore.Tests/ComponentGeneration/ComponentTests.cs +++ b/tests/GovUk.Frontend.AspNetCore.Tests/ComponentGeneration/ComponentTests.cs @@ -29,6 +29,13 @@ public void BackLink(ComponentTestCaseData data) => data, (generator, options) => generator.GenerateBackLink(options).ToHtmlString()); + [Theory] + [ComponentFixtureData("breadcrumbs", typeof(BreadcrumbsOptions))] + public void Breadcrumbs(ComponentTestCaseData data) => + CheckComponentHtmlMatchesExpectedHtml( + data, + (generator, options) => generator.GenerateBreadcrumbs(options).ToHtmlString()); + [Theory] [ComponentFixtureData("button", typeof(ButtonOptions))] public void Button(ComponentTestCaseData data) => diff --git a/tests/GovUk.Frontend.AspNetCore.Tests/TagHelpers/BreadcrumbsItemTagHelperTests.cs b/tests/GovUk.Frontend.AspNetCore.Tests/TagHelpers/BreadcrumbsItemTagHelperTests.cs index df27f790..b3324dd8 100644 --- a/tests/GovUk.Frontend.AspNetCore.Tests/TagHelpers/BreadcrumbsItemTagHelperTests.cs +++ b/tests/GovUk.Frontend.AspNetCore.Tests/TagHelpers/BreadcrumbsItemTagHelperTests.cs @@ -16,6 +16,8 @@ public class BreadcrumbsItemTagHelperTests public async Task ProcessAsync_NoLink_AddsItemToContext() { // Arrange + var content = "The item"; + var breadcrumbsContext = new BreadcrumbsContext(); var context = new TagHelperContext( @@ -33,7 +35,7 @@ public async Task ProcessAsync_NoLink_AddsItemToContext() getChildContentAsync: (useCachedResult, encoder) => { var tagHelperContent = new DefaultTagHelperContent(); - tagHelperContent.SetHtmlContent("The item"); + tagHelperContent.SetHtmlContent(content); return Task.FromResult(tagHelperContent); }); @@ -45,13 +47,16 @@ public async Task ProcessAsync_NoLink_AddsItemToContext() // Assert var lastItem = breadcrumbsContext.Items.Last(); Assert.Null(lastItem.Href); - Assert.Equal("The item", lastItem.Content?.ToHtmlString()); + Assert.Equal(content, lastItem.Html?.ToHtmlString()); } [Fact] public async Task ProcessAsync_WithLink_AddsItemToContext() { // Arrange + var content = "The item"; + var href = "http://place.com"; + var breadcrumbsContext = new BreadcrumbsContext(); var context = new TagHelperContext( @@ -69,10 +74,10 @@ public async Task ProcessAsync_WithLink_AddsItemToContext() attributes, getChildContentAsync: (useCachedResult, encoder) => { - attributes.Add("href", "place.com"); + attributes.Add("href", href); var tagHelperContent = new DefaultTagHelperContent(); - tagHelperContent.SetHtmlContent("The item"); + tagHelperContent.SetHtmlContent(content); return Task.FromResult(tagHelperContent); }); @@ -83,7 +88,7 @@ public async Task ProcessAsync_WithLink_AddsItemToContext() // Assert var lastItem = breadcrumbsContext.Items.Last(); - Assert.Equal("place.com", lastItem.Href); - Assert.Equal("The item", lastItem.Content?.ToHtmlString()); + Assert.Equal(href, lastItem.Href?.ToHtmlString()); + Assert.Equal(content, lastItem.Html?.ToHtmlString()); } } diff --git a/tests/GovUk.Frontend.AspNetCore.Tests/TagHelpers/BreadcrumbsTagHelperTests.cs b/tests/GovUk.Frontend.AspNetCore.Tests/TagHelpers/BreadcrumbsTagHelperTests.cs index ea2a5960..3666ae60 100644 --- a/tests/GovUk.Frontend.AspNetCore.Tests/TagHelpers/BreadcrumbsTagHelperTests.cs +++ b/tests/GovUk.Frontend.AspNetCore.Tests/TagHelpers/BreadcrumbsTagHelperTests.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; using System.Threading.Tasks; +using GovUk.Frontend.AspNetCore.ComponentGeneration; using GovUk.Frontend.AspNetCore.HtmlGeneration; using GovUk.Frontend.AspNetCore.TagHelpers; using GovUk.Frontend.AspNetCore.TestCommon; using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Razor.TagHelpers; +using Moq; using Xunit; namespace GovUk.Frontend.AspNetCore.Tests.TagHelpers; @@ -12,9 +14,27 @@ namespace GovUk.Frontend.AspNetCore.Tests.TagHelpers; public class BreadcrumbsTagHelperTests { [Fact] - public async Task ProcessAsync_GeneratesExpectedOutput() + public async Task ProcessAsync_InvokesComponentGeneratorWithExpectedOptions() { // Arrange + BreadcrumbsOptionsItem[] items = + [ + new BreadcrumbsOptionsItem() + { + Href = new HtmlString("first"), + Html = new HtmlString("First") + }, + new BreadcrumbsOptionsItem() + { + Href = new HtmlString("second"), + Html = new HtmlString("Second") + }, + new BreadcrumbsOptionsItem() + { + Html = new HtmlString("Last") + } + ]; + var context = new TagHelperContext( tagName: "govuk-breadcrumbs", allAttributes: new TagHelperAttributeList(), @@ -28,42 +48,31 @@ public async Task ProcessAsync_GeneratesExpectedOutput() { var breadcrumbsContext = context.GetContextItem(); - breadcrumbsContext.AddItem(new BreadcrumbsItem() - { - Href = "first", - Content = new HtmlString("First") - }); - - breadcrumbsContext.AddItem(new BreadcrumbsItem() - { - Href = "second", - Content = new HtmlString("Second") - }); - - breadcrumbsContext.AddItem(new BreadcrumbsItem() + foreach (var item in items) { - Content = new HtmlString("Last") - }); + breadcrumbsContext.AddItem(item); + } var tagHelperContent = new DefaultTagHelperContent(); return Task.FromResult(tagHelperContent); }); - var tagHelper = new BreadcrumbsTagHelper(); + var componentGeneratorMock = new Mock() { CallBase = true }; + BreadcrumbsOptions? actualOptions = null; + componentGeneratorMock.Setup(mock => mock.GenerateBreadcrumbs(It.IsAny())).Callback(o => actualOptions = o); + + var tagHelper = new BreadcrumbsTagHelper(componentGeneratorMock.Object); // Act await tagHelper.ProcessAsync(context, output); // Assert - var expectedHtml = @" -
-
    -
  1. First
  2. -
  3. Second
  4. -
  5. Last
  6. -
-
"; - - AssertEx.HtmlEqual(@expectedHtml, output.ToHtmlString()); + Assert.NotNull(actualOptions); + Assert.NotNull(actualOptions.Items); + Assert.Collection( + actualOptions.Items, + item => Assert.Same(items[0], item), + item => Assert.Same(items[1], item), + item => Assert.Same(items[2], item)); } }