Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions src/Ramstack.HtmxToolkit/Internal/EnumHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,25 +178,44 @@ or HtmxFetchMode.Cors
}

/// <summary>
/// Parses a string into a <see cref="HtmxSwap" /> value.
/// Parses the swap style at the start of a swap expression into a <see cref="HtmxSwap" /> value.
/// </summary>
/// <param name="expression">The string to parse.</param>
/// <param name="expression">The swap expression to parse.</param>
/// <returns>
/// The parsed <see cref="HtmxSwap" /> value if successful;
/// otherwise, <see langword="null" />.
/// </returns>
/// <remarks>
/// Like HTMX, ignores leading whitespace, treats any whitespace as the separator
/// before swap modifiers, and compares the style case-sensitively.
/// Only the values returned by <see cref="GetSwapValue(HtmxSwap)" /> are recognized.
/// </remarks>
public static HtmxSwap? ParseHtmxSwap(string? expression)
{
expression ??= "";
var s = expression.AsSpan().TrimStart();

var index = expression.IndexOf(' ');
if (index < 0)
index = expression.Length;
var index = 0;
while ((uint)index < (uint)s.Length && !char.IsWhiteSpace(s[index]))
index++;

var s = expression.AsSpan(0, index);
if (Enum.TryParse<HtmxSwap>(s, ignoreCase: true, out var value))
return value;
if ((uint)index < (uint)s.Length)
s = s[..index];

return null;
return s switch
{
"innerHTML" => HtmxSwap.InnerHtml,
"outerHTML" => HtmxSwap.OuterHtml,
"innerMorph" => HtmxSwap.InnerMorph,
"outerMorph" => HtmxSwap.OuterMorph,
"outerSync" => HtmxSwap.OuterSync,
"textContent" => HtmxSwap.TextContent,
"beforebegin" => HtmxSwap.BeforeBegin,
"afterbegin" => HtmxSwap.AfterBegin,
"beforeend" => HtmxSwap.BeforeEnd,
"afterend" => HtmxSwap.AfterEnd,
"delete" => HtmxSwap.Delete,
"none" => HtmxSwap.None,
_ => null
};
}
}
8 changes: 5 additions & 3 deletions tests/Ramstack.HtmxToolkit.Tests/HtmxResponseHeadersTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,15 @@ public void Reswap_IsNull_WhenHeaderAbsent()
Assert.That(headers.Reswap, Is.Null);
}

[Test]
public void Reswap_IsNull_WhenHeaderUnknown()
[TestCase("bogus")]
[TestCase("OuterHTML")]
[TestCase("1")]
public void Reswap_IsNull_WhenHeaderUnknown(string value)
{
var context = TestHelper.CreateHttpContext();
var headers = context.Response.GetHtmxHeaders();

context.Response.Headers[HtmxResponseHeaderNames.Reswap] = "bogus";
context.Response.Headers[HtmxResponseHeaderNames.Reswap] = value;

Assert.That(headers.Reswap, Is.Null);
}
Expand Down
44 changes: 32 additions & 12 deletions tests/Ramstack.HtmxToolkit.Tests/Internal/EnumHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,41 @@ public void GetSwapValue_ReturnsNull_ForNullValue()
public void ParseHtmxSwap_ParsesValue(string expression, HtmxSwap expected) =>
Assert.That(EnumHelper.ParseHtmxSwap(expression), Is.EqualTo(expected));

[Test]
public void ParseHtmxSwap_IgnoresModifiers() =>
Assert.That(EnumHelper.ParseHtmxSwap("innerHTML show:#content"), Is.EqualTo(HtmxSwap.InnerHtml));
[TestCase("innerHTML show:#content", HtmxSwap.InnerHtml)]
[TestCase("outerHTML\tsettle:200ms", HtmxSwap.OuterHtml)]
[TestCase("beforeend\n swap:1s", HtmxSwap.BeforeEnd)]
public void ParseHtmxSwap_IgnoresModifiers(string expression, HtmxSwap expected) =>
Assert.That(EnumHelper.ParseHtmxSwap(expression), Is.EqualTo(expected));

[Test]
public void ParseHtmxSwap_IsCaseInsensitive() =>
Assert.That(EnumHelper.ParseHtmxSwap("OuterHTML"), Is.EqualTo(HtmxSwap.OuterHtml));
[TestCase(" outerHTML", HtmxSwap.OuterHtml)]
[TestCase("\t outerHTML show:top", HtmxSwap.OuterHtml)]
public void ParseHtmxSwap_IgnoresLeadingWhitespace(string expression, HtmxSwap expected) =>
Assert.That(EnumHelper.ParseHtmxSwap(expression), Is.EqualTo(expected));

[Test]
public void ParseHtmxSwap_ReturnsNull_ForUnknownValue() =>
Assert.That(EnumHelper.ParseHtmxSwap("bogus"), Is.Null);
[TestCase("OuterHTML")]
[TestCase("outerhtml")]
[TestCase("InnerHtml")]
[TestCase("BEFOREEND")]
public void ParseHtmxSwap_IsCaseSensitive(string expression) =>
Assert.That(EnumHelper.ParseHtmxSwap(expression), Is.Null);

[Test]
public void ParseHtmxSwap_ReturnsNull_ForNullExpression() =>
Assert.That(EnumHelper.ParseHtmxSwap(null), Is.Null);
[TestCase("bogus")]
[TestCase("1")]
[TestCase("42")]
[TestCase("-1")]
[TestCase("innerHTML,outerHTML")]
[TestCase("before")]
[TestCase("append")]
public void ParseHtmxSwap_ReturnsNull_ForUnknownValue(string expression) =>
Assert.That(EnumHelper.ParseHtmxSwap(expression), Is.Null);

[TestCase(null)]
[TestCase("")]
[TestCase(" ")]
[TestCase("show:top")]
[TestCase("swap:1s settle:200ms")]
public void ParseHtmxSwap_ReturnsNull_WithoutStyle(string? expression) =>
Assert.That(EnumHelper.ParseHtmxSwap(expression), Is.Null);

[TestCase(HttpVerb.Get, "get")]
[TestCase(HttpVerb.Head, "head")]
Expand Down
Loading