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
54 changes: 54 additions & 0 deletions src/SmartFormat.Tests/Core/CharSetTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Linq;
using NUnit.Framework;
using SmartFormat.Core.Parsing;

namespace SmartFormat.Tests.Core;

[TestFixture]
internal class CharSetTests
{
[Test]
public void CharSet_Add_Remove()
{
char[] asciiChars = ['A', 'B', 'C'];
char[] nonAsciiChars = ['Ā', 'Б', '中'];
var charSet = new CharSet();
charSet.AddRange(asciiChars.AsEnumerable());
charSet.AddRange(nonAsciiChars.AsSpan());
var countBeforeRemoval = charSet.Count;
var existingRemoved = charSet.Remove('C');
charSet.Remove('中');
// trying to remove a not existing char returns false
var nonExistingRemoved = charSet.Remove('?');
var count = charSet.Count;

Assert.Multiple(() =>
{
Assert.That(countBeforeRemoval, Is.EqualTo(asciiChars.Length + nonAsciiChars.Length));
Assert.That(count, Is.EqualTo(countBeforeRemoval - 2));
Assert.That(existingRemoved, Is.True);
Assert.That(nonExistingRemoved, Is.False);
});
}

[Test]
public void CharSet_CreateFromSpan_GetCharacters_Contains()
{
char[] asciiAndNonAscii = ['\0', 'A', 'B', 'C', 'Ā', 'Б', '中'];
var charSet = new CharSet(asciiAndNonAscii.AsSpan());

Assert.Multiple(() =>
{
Assert.That(charSet, Has.Count.EqualTo(7));
Assert.That(charSet.Contains('A'), Is.True); // ASCII
Assert.That(charSet.Contains('\0'), Is.True); // control character
Assert.That(charSet.Contains('中'), Is.True); // non-ASCII
Assert.That(charSet.Contains('?'), Is.False);
Assert.That(charSet.GetCharacters(), Is.EquivalentTo(asciiAndNonAscii));
charSet.Clear();
Assert.That(charSet, Has.Count.EqualTo(0));
Assert.That(charSet.GetCharacters(), Is.Empty);
});
}
}
107 changes: 91 additions & 16 deletions src/SmartFormat.Tests/Core/ParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ public void Parser_Error_Action_Ignore()
// | Literal | Erroneous | | Okay |
var invalidTemplate = "Hello, I'm {Name from {City} {Street}";

// settings must be set before parser instantiation
var parser = GetRegularParser(new SmartSettings {Parser = new ParserSettings {ErrorAction = ParseErrorAction.Ignore}});
using var parsed = parser.ParseFormat(invalidTemplate);

Expand All @@ -177,6 +178,7 @@ public void Parser_Error_Action_Ignore()
[TestCase("Hello, I'm {Name from {City} {Street", false)]
public void Parser_Error_Action_MaintainTokens(string invalidTemplate, bool lastItemIsPlaceholder)
{
// settings must be set before parser instantiation
var parser = GetRegularParser(new SmartSettings {Parser = new ParserSettings {ErrorAction = ParseErrorAction.MaintainTokens}});
using var parsed = parser.ParseFormat(invalidTemplate);

Expand All @@ -203,14 +205,21 @@ public void Parser_Error_Action_MaintainTokens(string invalidTemplate, bool last
public void Parser_Error_Action_OutputErrorInResult()
{
// | Literal | Erroneous |
// ▼ Selector must not contain {
var invalidTemplate = "Hello, I'm {Name from {City}";

var parser = GetRegularParser(new SmartSettings {Parser = new ParserSettings {ErrorAction = ParseErrorAction.OutputErrorInResult}});

var parser = GetRegularParser(new SmartSettings
{
Parser = new ParserSettings
{
SelectorCharFilter = FilterType.Allowlist, // default
ErrorAction = ParseErrorAction.OutputErrorInResult
}
});

using var parsed = parser.ParseFormat(invalidTemplate);

Assert.That(parsed.Items, Has.Count.EqualTo(1));
Assert.That(parsed.Items[0].RawText, Does.StartWith("The format string has 1 issue"));
Assert.That(parsed.Items[0].RawText, Does.StartWith("The format string has 3 issues"));
}

[Test]
Expand Down Expand Up @@ -414,11 +423,11 @@ public void Parser_NotifyParsingError()
});

formatter.Parser.OnParsingFailure += (o, args) => parsingError = args.Errors;
var res = formatter.Format("{NoName {Other} {Same", default(object)!);
var res = formatter.Format("{NoName {Other} {Same");
Assert.Multiple(() =>
{
Assert.That(parsingError!.Issues, Has.Count.EqualTo(2));
Assert.That(parsingError.Issues[1].Issue, Is.EqualTo(new Parser.ParsingErrorText()[SmartFormat.Core.Parsing.Parser.ParsingError.MissingClosingBrace]));
Assert.That(parsingError!.Issues, Has.Count.EqualTo(3));
Assert.That(parsingError.Issues[2].Issue, Is.EqualTo(new Parser.ParsingErrorText()[Parser.ParsingError.MissingClosingBrace]));
});
}

Expand Down Expand Up @@ -459,6 +468,18 @@ public void Escaping_TheEscapingCharacter_ShouldWork()
Assert.That(result, Is.EqualTo(@"\\aaa\{}bbb ccc\x{}ddd\\"));
}

[Test]
public void Parsing_Selector_With_CharFromBlocklist_ShouldThrow()
{
var settings = new SmartSettings { Parser = new ParserSettings { SelectorCharFilter = FilterType.Blocklist } };
var parser = GetRegularParser(settings);

// The newline character is in the default blocklist of disallowed characters
Assert.That(() => parser.ParseFormat("{A\nB}"),
Throws.Exception.InstanceOf<ParsingErrors>().And.Message
.Contains(new Parser.ParsingErrorText()[Parser.ParsingError.InvalidCharactersInSelector]));
}

[Test]
public void StringFormat_Escaping_In_Literal()
{
Expand Down Expand Up @@ -536,8 +557,10 @@ public void Parse_Unicode(string formatString, string unicodeLiteral, int itemIn
[TestCase("{%C}", '%')]
public void Selector_With_Custom_Selector_Character(string formatString, char customChar)
{
// settings must be set before parser instantiation
var settings = new SmartSettings();
settings.Parser.AddCustomSelectorChars(new[]{customChar});
settings.Parser.AddCustomSelectorChars([customChar]);
var x = settings.Parser.GetSelectorChars();
var parser = GetRegularParser(settings);
var result = parser.ParseFormat(formatString);

Expand All @@ -546,7 +569,7 @@ public void Selector_With_Custom_Selector_Character(string formatString, char cu
Assert.That(placeholder!.Selectors, Has.Count.EqualTo(1));
Assert.Multiple(() =>
{
Assert.That(placeholder!.Selectors, Has.Count.EqualTo(placeholder!.GetSelectors().Count));
Assert.That(placeholder.Selectors, Has.Count.EqualTo(placeholder.GetSelectors().Count));
Assert.That(placeholder.Selectors[0].ToString(), Is.EqualTo(formatString.Substring(1, 2)));
});
}
Expand All @@ -555,8 +578,10 @@ public void Selector_With_Custom_Selector_Character(string formatString, char cu
[TestCase("{a°b}", '°')]
public void Selectors_With_Custom_Operator_Character(string formatString, char customChar)
{
var parser = GetRegularParser();
parser.Settings.Parser.AddCustomOperatorChars(new[]{customChar});
// settings must be set before parser instantiation
var settings = new SmartSettings();
settings.Parser.AddCustomOperatorChars([customChar]);
var parser = GetRegularParser(settings);
var result = parser.ParseFormat(formatString);

var placeholder = result.Items[0] as Placeholder;
Expand All @@ -583,10 +608,12 @@ public void Selector_WorksWithAllUnicodeChars(string selector)
{
// See https://github.com/axuno/SmartFormat/issues/454

// settings must be set before parser instantiation
var settings = new SmartSettings { Parser = { SelectorCharFilter = FilterType.Blocklist } };
const string expected = "The Value";
// The default formatter with default settings should be able to handle any
// Unicode characters in selectors except the "magic" disallowed ones
var formatter = Smart.CreateDefaultSmartFormat();
var formatter = Smart.CreateDefaultSmartFormat(settings);
// Use the Unicode string as a selector of the placeholder
var template = $"{{{selector}}}";
var result = formatter.Format(template, new Dictionary<string, string> { { selector, expected } });
Expand Down Expand Up @@ -647,10 +674,11 @@ public void Selector_With_Nullable_Operator_Character(string formatString)
public void Selector_With_Other_Contiguous_Operator_Characters(string formatString, char customChar)
{
// contiguous operator characters are parsed as "ONE operator string"

var parser = GetRegularParser();
var settings = new SmartSettings();
settings.Parser.AddCustomOperatorChars([customChar]);
var parser = GetRegularParser(settings);
// adding '.' is ignored, as it's a standard operator
parser.Settings.Parser.AddCustomOperatorChars(new[]{customChar});
parser.Settings.Parser.AddCustomOperatorChars([customChar]);
var result = parser.ParseFormat(formatString);

var placeholder = result.Items[0] as Placeholder;
Expand Down Expand Up @@ -706,6 +734,12 @@ public void ParseInputAsHtml(string input)
Assert.That(literalText!.RawText, Is.EqualTo(input));
}

#region * Parse HTML input without ParserSetting 'IsHtml'

/// <summary>
/// <see cref="ParserSettings.SelectorCharFilter"/> is <see cref="FilterType.Blocklist"/>:
/// all characters are allowed in selectors
/// </summary>
[TestCase("<script>{Placeholder}</script>", "{Placeholder}")]
[TestCase("<style>{Placeholder}</style>", "{Placeholder}")]
[TestCase("Something <style>h1 { color : #000; }</style>! nice", "{ color : #000; }")]
Expand All @@ -715,7 +749,12 @@ public void ParseHtmlInput_Without_ParserSetting_IsHtml(string input, string sel
var parser = GetRegularParser(new SmartSettings
{
StringFormatCompatibility = false,
Parser = new ParserSettings { ErrorAction = ParseErrorAction.ThrowError, ParseInputAsHtml = false }
Parser = new ParserSettings
{
SelectorCharFilter = FilterType.Blocklist,
ErrorAction = ParseErrorAction.ThrowError,
ParseInputAsHtml = false
}
});

var result = parser.ParseFormat(input);
Expand All @@ -724,9 +763,45 @@ public void ParseHtmlInput_Without_ParserSetting_IsHtml(string input, string sel
Assert.That(result.Items, Has.Count.EqualTo(3));
Assert.That(((Placeholder) result.Items[1]).RawText, Is.EqualTo(selector));
});
}

/// <summary>
/// <see cref="ParserSettings.SelectorCharFilter"/> is <see cref="FilterType.Allowlist"/>:
/// Predefined set of allowed characters in selectors
/// </summary>
[TestCase("<script>{Placeholder}</script>", false)] // should parse a placeholder
[TestCase("<style>{Placeholder}</style>", false)] // should parse a placeholder
[TestCase("Something <style>h1 { color : #000; }</style>! nice", true)] // illegal selector chars
[TestCase("Something <script>{const a = '</script>';}</script>! nice", true)] // illegal selector chars
public void ParseHtmlInput_Without_ParserSetting_IsHtml(string input, bool shouldThrow)
{
var parser = GetRegularParser(new SmartSettings
{
StringFormatCompatibility = false,
Parser = new ParserSettings
{
SelectorCharFilter = FilterType.Allowlist,
ErrorAction = ParseErrorAction.ThrowError,
ParseInputAsHtml = false
}
});

switch (shouldThrow)
{
case true:
Assert.That(() => _ = parser.ParseFormat(input), Throws.TypeOf<ParsingErrors>());
break;
case false:
{
var result = parser.ParseFormat(input);
Assert.That(result.Items, Has.Count.EqualTo(3));
break;
}
}
}

#endregion

/// <summary>
/// SmartFormat is able to parse script tags, if <see cref="ParserSettings.ParseInputAsHtml"/> is <see langword="true"/>
/// </summary>
Expand Down
13 changes: 7 additions & 6 deletions src/SmartFormat.Tests/Core/SettingsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ public void ExistingSelectorCharacter_Should_Not_Be_Added()
Assert.Multiple(() =>
{
Assert.That(settings.Parser.CustomSelectorChars.Count(c => c == 'A'), Is.EqualTo(0));
Assert.That(settings.Parser.CustomSelectorChars.Count(c => c == ' '), Is.EqualTo(0));
Assert.That(settings.Parser.CustomSelectorChars.Count(c => c == ' '), Is.EqualTo(1));
});
}

[Test]
public void ControlCharacters_Should_Be_Added_As_SelectorChars()
[TestCase(FilterType.Allowlist)]
[TestCase(FilterType.Blocklist)]
public void ControlCharacters_Should_Be_Added_As_SelectorChars(FilterType filterType)
{
var settings = new SmartSettings();
var settings = new SmartSettings { Parser = { SelectorCharFilter = filterType } };
var controlChars = ParserSettings.ControlChars().ToList();
settings.Parser.AddCustomSelectorChars(controlChars);

Expand All @@ -39,8 +40,8 @@ public void ControlCharacters_Should_Be_Added_As_SelectorChars()
Assert.That(settings.Parser.CustomSelectorChars, Has.Count.EqualTo(controlChars.Count));
foreach (var c in settings.Parser.CustomSelectorChars)
{
Assert.That(settings.Parser.DisallowedSelectorChars(), Does.Not.Contain(c),
$"Control char U+{(int)c:X4} should be allowed as selector char.");
Assert.That(settings.Parser.GetSelectorChars(), filterType == FilterType.Allowlist ? Does.Contain(c) : Does.Not.Contain(c),
$"Control char U+{(int) c:X4} should be allowed as selector char.");
}
});
}
Expand Down
Loading