From 82cc2bb627387c36dd067f16214f6f7bab1f9718 Mon Sep 17 00:00:00 2001
From: Mitchel Sellers <msellers@Iowacomputergurus.com>
Date: Thu, 3 Aug 2023 17:08:56 -0500
Subject: [PATCH] Fixes #20 by adding back the helper

---
 .../Views/Home/Index.cshtml                   |  8 +-
 .../Card/CardHeaderActionsTagHelperTests.cs   | 81 +++++++++++++++++++
 .../Card/CardHeaderActionsTagHelper.cs        | 50 ++++++++++++
 .../Card/CardHeaderTagHelper.cs               | 29 ++++++-
 4 files changed, 163 insertions(+), 5 deletions(-)
 create mode 100644 src/AspNetCore.Utilities.Bootstrap5TagHelpers.Tests/Card/CardHeaderActionsTagHelperTests.cs
 create mode 100644 src/AspNetCore.Utilities.Bootstrap5TagHelpers/Card/CardHeaderActionsTagHelper.cs

diff --git a/src/AspNetCore.Utilities.Bootstrap5TagHelpers.Sample/Views/Home/Index.cshtml b/src/AspNetCore.Utilities.Bootstrap5TagHelpers.Sample/Views/Home/Index.cshtml
index 4b0ec6a..f5d3623 100644
--- a/src/AspNetCore.Utilities.Bootstrap5TagHelpers.Sample/Views/Home/Index.cshtml
+++ b/src/AspNetCore.Utilities.Bootstrap5TagHelpers.Sample/Views/Home/Index.cshtml
@@ -289,8 +289,8 @@
                 <card>
                     <card-header title="Testing All Options">
                         <card-header-actions>
-                            <a href="https://www.google.com" class="btn btn-primary">Google</a>
-                            <a href="https://www.microsoft.com" class="btn btn-secondary">Microsoft</a>
+                            <a href="https://www.google.com" class="btn btn-sm btn-primary me-1">Google</a>
+                            <a href="https://www.microsoft.com" class="btn btn-sm btn-secondary">Microsoft</a>
                         </card-header-actions>
                     </card-header>
                     <card-body>
@@ -306,8 +306,8 @@
                     &lt;card&gt;
                     &lt;card-header title=&quot;Testing All Options&quot;&gt;
                     &lt;card-header-actions&gt;
-                    &lt;a href=&quot;https://www.google.com&quot; class=&quot;btn btn-primary&quot;&gt;Google&lt;/a&gt;
-                    &lt;a href=&quot;https://www.microsoft.com&quot; class=&quot;btn btn-secondary&quot;&gt;Microsoft&lt;/a&gt;
+                    &lt;a href=&quot;https://www.google.com&quot; class=&quot;btn btn-sm btn-primary me-1&quot;&gt;Google&lt;/a&gt;
+                    &lt;a href=&quot;https://www.microsoft.com&quot; class=&quot;btn btn-sm btn-secondary&quot;&gt;Microsoft&lt;/a&gt;
                     &lt;/card-header-actions&gt;
                     &lt;/card-header&gt;
                     &lt;card-body&gt;
diff --git a/src/AspNetCore.Utilities.Bootstrap5TagHelpers.Tests/Card/CardHeaderActionsTagHelperTests.cs b/src/AspNetCore.Utilities.Bootstrap5TagHelpers.Tests/Card/CardHeaderActionsTagHelperTests.cs
new file mode 100644
index 0000000..c51e0b2
--- /dev/null
+++ b/src/AspNetCore.Utilities.Bootstrap5TagHelpers.Tests/Card/CardHeaderActionsTagHelperTests.cs
@@ -0,0 +1,81 @@
+using ICG.AspNetCore.Utilities.Bootstrap5TagHelpers.Card;
+using ICG.AspNetCore.Utilities.Bootstrap5TagHelpers.Contexts;
+using Microsoft.AspNetCore.Razor.TagHelpers;
+
+namespace ICG.AspNetCore.Utilities.Bootstrap5TagHelpers.Tests.Card;
+public class CardHeaderActionsTagHelperTests : AbstractTagHelperTest
+{
+    [Fact]
+    public async Task Should_ThrowException_WhenMissingContext()
+    {
+        //Arrange
+        var context = MakeTagHelperContext();
+        var output = MakeTagHelperOutput(" ");
+
+        //Act
+        var helper = new CardHeaderActionsTagHelper();
+        var exceptionResult = await Record.ExceptionAsync(() => helper.ProcessAsync(context, output));
+
+        Assert.NotNull(exceptionResult);
+        Assert.IsType<KeyNotFoundException>(exceptionResult);
+    }
+
+    [Fact]
+    public async Task Should_ThrowException_WhenContextIsNull()
+    {
+        //Arrange
+        var context = MakeTagHelperContext();
+        context.Items.Add(typeof(CardContext), null);
+        var output = MakeTagHelperOutput(" ");
+
+        //Act
+        var helper = new CardHeaderActionsTagHelper();
+        var exceptionResult = await Record.ExceptionAsync(() => helper.ProcessAsync(context, output));
+
+        Assert.NotNull(exceptionResult);
+        Assert.IsType<ArgumentException>(exceptionResult);
+    }
+
+    [Theory]
+    [InlineData("d-flex")]
+    [InlineData("flex-nowrap")]
+    [InlineData("mt-2")]
+    [InlineData("mt-sm-0")]
+    public async Task Should_Render_With_ClassAdded(string expectedClass)
+    {
+        //Arrange
+        var context = MakeTagHelperContext();
+        context.Items.Add(typeof(CardContext), new CardContext());
+        var output = MakeTagHelperOutput(" ");
+
+        //Act
+        var helper = new CardHeaderActionsTagHelper();
+        await helper.ProcessAsync(context, output);
+
+        //Assert
+        var classValue = output.Attributes["class"].Value;
+        Assert.NotNull(classValue);
+        var classString = classValue.ToString();
+        Assert.True(classString?.Contains(expectedClass));
+    }
+
+    [Fact]
+    public async Task Should_Render_With_ClassAdded_PreservingCustomClasses()
+    {
+        //Arrange
+        var customClass = "testing-out";
+        var expectedClass = $"{customClass} d-flex flex-nowrap mt-2 mt-sm-0";
+        var existingAttributes = new TagHelperAttributeList(new List<TagHelperAttribute>
+            {new("class", customClass)});
+        var context = MakeTagHelperContext();
+        context.Items.Add(typeof(CardContext), new CardContext());
+        var output = MakeTagHelperOutput(" ", existingAttributes);
+
+        //Act
+        var helper = new CardHeaderActionsTagHelper();
+        await helper.ProcessAsync(context, output);
+
+        //Assert
+        Assert.Equal(expectedClass, output.Attributes["class"].Value.ToString());
+    }
+}
\ No newline at end of file
diff --git a/src/AspNetCore.Utilities.Bootstrap5TagHelpers/Card/CardHeaderActionsTagHelper.cs b/src/AspNetCore.Utilities.Bootstrap5TagHelpers/Card/CardHeaderActionsTagHelper.cs
new file mode 100644
index 0000000..b49567e
--- /dev/null
+++ b/src/AspNetCore.Utilities.Bootstrap5TagHelpers/Card/CardHeaderActionsTagHelper.cs
@@ -0,0 +1,50 @@
+using ICG.AspNetCore.Utilities.Bootstrap5TagHelpers.Contexts;
+using Microsoft.AspNetCore.Mvc.TagHelpers;
+using Microsoft.AspNetCore.Razor.TagHelpers;
+using System.Text.Encodings.Web;
+using System.Threading.Tasks;
+using System;
+
+namespace ICG.AspNetCore.Utilities.Bootstrap5TagHelpers.Card;
+
+/// <summary>
+/// Helper for rendering actions within the header of a card
+/// </summary>
+[RestrictChildren("button", "a")]
+public class CardHeaderActionsTagHelper : TagHelper
+{
+    /// <summary>
+    /// Renders the control
+    /// </summary>
+    /// <param name="context"></param>
+    /// <param name="output"></param>
+    /// <returns></returns>
+    /// <exception cref="ArgumentException"></exception>
+    public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
+    {
+        //Get the context information
+        var cardContext = context.Items[typeof(CardContext)] as CardContext;
+        if (cardContext == null)
+            throw new ArgumentException("CardContext is not specified in context parameter");
+
+        return ProcessAsyncInternal(output);
+    }
+
+    /// <summary>
+    /// Internal implementation
+    /// </summary>
+    /// <param name="output"></param>
+    /// <returns></returns>
+    private static async Task ProcessAsyncInternal(TagHelperOutput output)
+    {
+        output.TagName = "div";
+        output.AddClass("d-flex", HtmlEncoder.Default);
+        output.AddClass("flex-nowrap", HtmlEncoder.Default);
+        output.AddClass("mt-2", HtmlEncoder.Default);
+        output.AddClass("mt-sm-0", HtmlEncoder.Default);
+
+        var content = (await output.GetChildContentAsync()).GetContent();
+
+        output.Content.AppendHtml(content);
+    }
+}
\ No newline at end of file
diff --git a/src/AspNetCore.Utilities.Bootstrap5TagHelpers/Card/CardHeaderTagHelper.cs b/src/AspNetCore.Utilities.Bootstrap5TagHelpers/Card/CardHeaderTagHelper.cs
index 18f7d5f..ed756b1 100644
--- a/src/AspNetCore.Utilities.Bootstrap5TagHelpers/Card/CardHeaderTagHelper.cs
+++ b/src/AspNetCore.Utilities.Bootstrap5TagHelpers/Card/CardHeaderTagHelper.cs
@@ -1,9 +1,11 @@
 using ICG.AspNetCore.Utilities.Bootstrap5TagHelpers.Contexts;
+using Microsoft.AspNetCore.Components.Web;
 using Microsoft.AspNetCore.Mvc.Rendering;
 using Microsoft.AspNetCore.Mvc.TagHelpers;
 using Microsoft.AspNetCore.Razor.TagHelpers;
 using System;
 using System.Text.Encodings.Web;
+using System.Threading.Tasks;
 
 namespace ICG.AspNetCore.Utilities.Bootstrap5TagHelpers.Card
 {
@@ -18,6 +20,7 @@ public class CardHeaderTagHelper : TagHelper
         /// </summary>
         public string Title { get; set; }
 
+
         /// <summary>
         /// Renders the header for a bootstrap card
         /// </summary>
@@ -25,16 +28,34 @@ public class CardHeaderTagHelper : TagHelper
         /// <param name="output"></param>
         /// <returns></returns>
         /// <exception cref="ArgumentException"></exception>
-        public override void Process(TagHelperContext context, TagHelperOutput output)
+        public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
         {
             //Get the context information
             if (context.Items[typeof(CardContext)] is not CardContext cardContext)
                 throw new ArgumentException("CardContext is not specified in context parameter");
 
+            return ProcessAsyncInternal(output, cardContext);
+        }
+
+        private async Task ProcessAsyncInternal(TagHelperOutput output, CardContext cardContext)
+        {
             //Setup basic tag information
             output.TagName = "div";
             output.AddClass("card-header", HtmlEncoder.Default);
 
+            //Get sub controls if we need them
+            var body = (await output.GetChildContentAsync()).GetContent();
+            body = body.Trim();
+
+            if (!string.IsNullOrWhiteSpace(body))
+            {
+                output.AddClass("d-flex", HtmlEncoder.Default);
+                output.AddClass("flex-column", HtmlEncoder.Default);
+                output.AddClass("flex-sm-row", HtmlEncoder.Default);
+                output.AddClass("align-items-sm-center", HtmlEncoder.Default);
+                output.AddClass("justify-content-between", HtmlEncoder.Default);
+            }
+
             //If we have an id make a custom span
             if (!string.IsNullOrEmpty(cardContext.Id))
             {
@@ -47,6 +68,12 @@ public override void Process(TagHelperContext context, TagHelperOutput output)
             {
                 output.Content.AppendHtml(Title);
             }
+
+
+            //Add sub-content after our title
+            if (!string.IsNullOrEmpty(body))
+                output.Content.AppendHtml(body);
+            
         }
     }
 }