From 68a09824e439fddab2b3fbf25abf2bcc116a7529 Mon Sep 17 00:00:00 2001 From: rameel Date: Sat, 22 Aug 2026 01:26:41 +0500 Subject: [PATCH] Restore HtmlAttributeValueStyle.SingleQuotes and add regression tests The hx-request attribute lost its SingleQuotes style by mistake. Restore it and add a test for each JSON-emitting tag helper asserting the attribute uses SingleQuotes so this regression can't slip through again. --- .../TagHelpers/HtmxRequestTagHelper.cs | 4 +++- .../HtmxConfigTagHelperTests.cs | 11 +++++++++++ .../HtmxHeaderTagHelperTests.cs | 18 ++++++++++++++++++ .../HtmxRequestTagHelperTests.cs | 15 +++++++++++++++ .../HtmxValsTagHelperTests.cs | 18 ++++++++++++++++++ 5 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/Ramstack.HtmxToolkit/TagHelpers/HtmxRequestTagHelper.cs b/src/Ramstack.HtmxToolkit/TagHelpers/HtmxRequestTagHelper.cs index 749afed..79f3b2c 100644 --- a/src/Ramstack.HtmxToolkit/TagHelpers/HtmxRequestTagHelper.cs +++ b/src/Ramstack.HtmxToolkit/TagHelpers/HtmxRequestTagHelper.cs @@ -62,7 +62,9 @@ public override Task ProcessAsync(TagHelperContext context, TagHelperOutput outp { var info = HtmxRequestJsonSerializerContext.Default.HtmxRequestData; var request = new HtmlString(JsonSerializer.Serialize(_request, info)); - output.Attributes.SetAttribute(new TagHelperAttribute("hx-request", request)); + + output.Attributes.SetAttribute( + new TagHelperAttribute("hx-request", request, HtmlAttributeValueStyle.SingleQuotes)); } return Task.CompletedTask; diff --git a/tests/Ramstack.HtmxToolkit.Tests/HtmxConfigTagHelperTests.cs b/tests/Ramstack.HtmxToolkit.Tests/HtmxConfigTagHelperTests.cs index 372195d..904ce5f 100644 --- a/tests/Ramstack.HtmxToolkit.Tests/HtmxConfigTagHelperTests.cs +++ b/tests/Ramstack.HtmxToolkit.Tests/HtmxConfigTagHelperTests.cs @@ -49,6 +49,17 @@ public async Task ProcessAsync_WithDefaults_SerializesEmptyObject() Assert.That(GetContent(output), Is.EqualTo("{}")); } + [Test] + public async Task ProcessAsync_UsesSingleQuotesForJsonAttribute() + { + var output = TestHelper.CreateTagHelperOutput(); + var helper = new HtmxConfigTagHelper(new StubAntiforgery()); + + await helper.ProcessAsync(TestHelper.CreateTagHelperContext(), output); + + Assert.That(output.Attributes["content"]!.ValueStyle, Is.EqualTo(HtmlAttributeValueStyle.SingleQuotes)); + } + [Test] public async Task ProcessAsync_SerializesConfiguredOptions() { diff --git a/tests/Ramstack.HtmxToolkit.Tests/HtmxHeaderTagHelperTests.cs b/tests/Ramstack.HtmxToolkit.Tests/HtmxHeaderTagHelperTests.cs index 3186c9f..1a4e163 100644 --- a/tests/Ramstack.HtmxToolkit.Tests/HtmxHeaderTagHelperTests.cs +++ b/tests/Ramstack.HtmxToolkit.Tests/HtmxHeaderTagHelperTests.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Razor.TagHelpers; namespace Ramstack.HtmxToolkit.Tests; @@ -46,6 +47,23 @@ public void Headers_TreatsNamesAsCaseInsensitive() Assert.That(helper.Headers["X-CUSTOM"], Is.EqualTo("second")); } + [Test] + public async Task ProcessAsync_UsesSingleQuotesForJsonAttribute() + { + var output = TestHelper.CreateTagHelperOutput(); + var helper = new HtmxHeaderTagHelper + { + Headers = + { + ["X-Custom"] = "value" + } + }; + + await helper.ProcessAsync(TestHelper.CreateTagHelperContext(), output); + + Assert.That(output.Attributes["hx-headers"]!.ValueStyle, Is.EqualTo(HtmlAttributeValueStyle.SingleQuotes)); + } + [Test] public async Task ProcessAsync_SerializesEmptyDictionary() { diff --git a/tests/Ramstack.HtmxToolkit.Tests/HtmxRequestTagHelperTests.cs b/tests/Ramstack.HtmxToolkit.Tests/HtmxRequestTagHelperTests.cs index d3a2692..ef7fc01 100644 --- a/tests/Ramstack.HtmxToolkit.Tests/HtmxRequestTagHelperTests.cs +++ b/tests/Ramstack.HtmxToolkit.Tests/HtmxRequestTagHelperTests.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Razor.TagHelpers; namespace Ramstack.HtmxToolkit.Tests; @@ -49,6 +50,20 @@ public async Task ProcessAsync_OmitsUnsetProperties() Assert.That(json.ContainsKey("noHeaders"), Is.False); } + [Test] + public async Task ProcessAsync_UsesSingleQuotesForJsonAttribute() + { + var output = TestHelper.CreateTagHelperOutput(); + var helper = new HtmxRequestTagHelper + { + Timeout = 500 + }; + + await helper.ProcessAsync(TestHelper.CreateTagHelperContext(), output); + + Assert.That(output.Attributes["hx-request"]!.ValueStyle, Is.EqualTo(HtmlAttributeValueStyle.SingleQuotes)); + } + [Test] public async Task ProcessAsync_OmitsUnsetConfiguration() { diff --git a/tests/Ramstack.HtmxToolkit.Tests/HtmxValsTagHelperTests.cs b/tests/Ramstack.HtmxToolkit.Tests/HtmxValsTagHelperTests.cs index 871ad1b..c884e04 100644 --- a/tests/Ramstack.HtmxToolkit.Tests/HtmxValsTagHelperTests.cs +++ b/tests/Ramstack.HtmxToolkit.Tests/HtmxValsTagHelperTests.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Razor.TagHelpers; namespace Ramstack.HtmxToolkit.Tests; @@ -37,6 +38,23 @@ public async Task ProcessAsync_SerializesValues() Assert.That(json["sort"].GetString(), Is.EqualTo("title")); } + [Test] + public async Task ProcessAsync_UsesSingleQuotesForJsonAttribute() + { + var output = TestHelper.CreateTagHelperOutput(); + var helper = new HtmxValsTagHelper + { + Values = + { + ["sort"] = "title" + } + }; + + await helper.ProcessAsync(TestHelper.CreateTagHelperContext(), output); + + Assert.That(output.Attributes["hx-vals"]!.ValueStyle, Is.EqualTo(HtmlAttributeValueStyle.SingleQuotes)); + } + [Test] public async Task ProcessAsync_OmitsEmptyDictionary() {