using System; using System.Linq; using Xunit; namespace Server.Tests; /// /// Tests for the Html.EscapeHtml extension methods. /// These tests verify correct HTML entity escaping and edge cases. /// public class HtmlEscapeTests { [Theory(DisplayName = "No escaping needed")] [InlineData("")] [InlineData("Hello World")] [InlineData("Plain text without special characters")] [InlineData("123456789")] [InlineData("!@#$%^*()_+-=[]{}|;:,.?")] public void EscapeHtml_NoSpecialCharacters_ReturnsUnchanged(string input) { var result = input.EscapeHtml(); Assert.Equal(input, result); } [Theory(DisplayName = "Single character escaping")] [InlineData("<", "<")] [InlineData(">", ">")] [InlineData("&", "&")] [InlineData("\"", """)] [InlineData("'", "'")] public void EscapeHtml_SingleSpecialCharacter_EscapesCorrectly(string input, string expected) { var result = input.EscapeHtml(); Assert.Equal(expected, result); } [Theory(DisplayName = "Multiple instances of same character")] [InlineData("<><>", "<><>")] [InlineData("&&&&", "&&&&")] [InlineData("\"\"\"", """"")] [InlineData("'''", "'''")] [InlineData(">>>", ">>>")] public void EscapeHtml_MultipleSpecialCharacters_EscapesAll(string input, string expected) { var result = input.EscapeHtml(); Assert.Equal(expected, result); } [Theory(DisplayName = "Mixed content")] [InlineData("Hello ", "Hello <world>")] [InlineData("
Hello
", "<div>Hello</div>")] [InlineData("Tom & Jerry", "Tom & Jerry")] [InlineData("He said \"hello\"", "He said "hello"")] [InlineData("It's a test", "It's a test")] public void EscapeHtml_MixedContent_EscapesMixedSpecialCharacters(string input, string expected) { var result = input.EscapeHtml(); Assert.Equal(expected, result); } [Theory(DisplayName = "Starting with special character")] [InlineData("World", ">World")] [InlineData("&Start", "&Start")] [InlineData("\"Quote", ""Quote")] [InlineData("'Apostrophe", "'Apostrophe")] public void EscapeHtml_StartsWithSpecialCharacter_EscapesStart(string input, string expected) { var result = input.EscapeHtml(); Assert.Equal(expected, result); } [Theory(DisplayName = "Ending with special character")] [InlineData("Hello<", "Hello<")] [InlineData("World>", "World>")] [InlineData("End&", "End&")] [InlineData("Quote\"", "Quote"")] [InlineData("Test'", "Test'")] public void EscapeHtml_EndsWithSpecialCharacter_EscapesEnd(string input, string expected) { var result = input.EscapeHtml(); Assert.Equal(expected, result); } [Theory(DisplayName = "Complex mixed scenarios")] [InlineData("

Hello & goodbye

", "<p>Hello & goodbye</p>")] [InlineData("<already>", "&lt;already&gt;")] [InlineData("", "<tag attr="value" data='test'>")] [InlineData("ac&d\"e'f", "a<b>c&d"e'f")] [InlineData(" ", "&nbsp;")] public void EscapeHtml_ComplexScenarios_EscapesAllSpecialCharacters(string input, string expected) { var result = input.EscapeHtml(); Assert.Equal(expected, result); } [Fact(DisplayName = "Null string input")] public void EscapeHtml_NullString_ReturnsEmpty() { string? input = null; var result = input.EscapeHtml(); Assert.Empty(result); } [Fact(DisplayName = "Empty string input")] public void EscapeHtml_EmptyString_ReturnsEmpty() { var result = "".EscapeHtml(); Assert.Empty(result); } [Fact(DisplayName = "Only special characters")] public void EscapeHtml_OnlySpecialCharacters_EscapesAll() { const string input = "<>&\"'"; const string expected = "<>&"'"; var result = input.EscapeHtml(); Assert.Equal(expected, result); } [Fact(DisplayName = "Ampersand must be escaped first")] public void EscapeHtml_AmpersandFirst_PreventDoubleEscaping() { // This is critical: & must be escaped to & // If we're not careful, we could double-escape already-escaped content const string input = "<"; const string expected = "&lt;"; var result = input.EscapeHtml(); Assert.Equal(expected, result); } [Theory(DisplayName = "ReadOnlySpan overload - no special characters")] [InlineData("Hello World")] [InlineData("Plain text")] public void EscapeHtml_ReadOnlySpan_NoSpecialCharacters_ReturnsUnchanged(string input) { var result = input.AsSpan().EscapeHtml(); Assert.Equal(input, result); } [Theory(DisplayName = "ReadOnlySpan overload - with special characters")] [InlineData("
", "<div>")] [InlineData("Tom & Jerry", "Tom & Jerry")] public void EscapeHtml_ReadOnlySpan_WithSpecialCharacters_EscapesCorrectly(string input, string expected) { var result = input.AsSpan().EscapeHtml(); Assert.Equal(expected, result); } [Theory(DisplayName = "ReadOnlySpan overload - empty input")] [InlineData("")] public void EscapeHtml_ReadOnlySpan_Empty_ReturnsEmpty(string input) { var result = input.AsSpan().EscapeHtml(); Assert.Empty(result); } [Fact(DisplayName = "Consecutive special characters")] public void EscapeHtml_ConsecutiveSpecialCharacters_EscapesAll() { const string input = "<<>>&&\"\"''"; const string expected = "<<>>&&""''"; var result = input.EscapeHtml(); Assert.Equal(expected, result); } [Fact(DisplayName = "Special characters with single normal character between")] public void EscapeHtml_SpecialCharactersWithGaps_EscapesAll() { const string input = "b&c\"d'e"; const string expected = "<a>b&c"d'e"; var result = input.EscapeHtml(); Assert.Equal(expected, result); } [Fact(DisplayName = "HTML tags")] public void EscapeHtml_HtmlTags_EscapesTagBrackets() { var input = "Hello"; var expected = "<html><body>Hello</body></html>"; var result = input.EscapeHtml(); Assert.Equal(expected, result); } [Fact(DisplayName = "HTML attributes with mixed quotes")] public void EscapeHtml_HtmlAttributesWithQuotes_EscapesCorrectly() { var input = ""; var expected = "<a href="test" data='value'>"; var result = input.EscapeHtml(); Assert.Equal(expected, result); } [Theory(DisplayName = "Whitespace handling")] [InlineData(" spaces ", " spaces ")] [InlineData("\ttabs\t", "\ttabs\t")] [InlineData("\nnewlines\n", "\nnewlines\n")] public void EscapeHtml_Whitespace_PreservedAsIs(string input, string expected) { var result = input.EscapeHtml(); Assert.Equal(expected, result); } [Fact(DisplayName = "Performance: long string without special characters")] public void EscapeHtml_LongStringNoSpecialCharacters_ReturnsQuickly() { var input = new string('a', 10000); var result = input.EscapeHtml(); Assert.Equal(input, result); } [Fact(DisplayName = "Performance: long string with special characters")] public void EscapeHtml_LongStringWithSpecialCharacters_HandlesCorrectly() { var input = $"Start{new string('<', 100)}End{new string('&', 100)}Final"; var expected = $"Start{string.Join("", Enumerable.Repeat("<", 100))}End{string.Join("", Enumerable.Repeat("&", 100))}Final"; var result = input.EscapeHtml(); Assert.Equal(expected, result); } [Fact(DisplayName = "Unicode characters")] public void EscapeHtml_UnicodeCharacters_PreservedWithSpecialCharsEscaped() { const string input = "Hello δΈ–η•Œ & πŸŽ‰"; const string expected = "Hello δΈ–η•Œ <test> & πŸŽ‰"; var result = input.EscapeHtml(); Assert.Equal(expected, result); } [Fact(DisplayName = "String overload matches ReadOnlySpan overload")] public void EscapeHtml_StringVsReadOnlySpan_ProduceSameResult() { const string input = "
Tom & Jerry 'in' \"quotes\"
"; var resultString = input.EscapeHtml(); var resultSpan = input.AsSpan().EscapeHtml(); Assert.Equal(resultString, resultSpan); } }