diff --git a/Pipeline/Build.cs b/Pipeline/Build.cs
index a07a0cc08..25356ef59 100644
--- a/Pipeline/Build.cs
+++ b/Pipeline/Build.cs
@@ -19,7 +19,7 @@ partial class Build : NukeBuild
///
/// Afterward, you can update the package reference in `Directory.Packages.props` and reset this flag.
///
- readonly BuildScope BuildScope = BuildScope.Default;
+ readonly BuildScope BuildScope = BuildScope.CoreOnly;
[Parameter("Github Token")] readonly string GithubToken;
diff --git a/Source/aweXpect.Core/Core/StringDifference.cs b/Source/aweXpect.Core/Core/StringDifference.cs
index 3e3795e3e..91da9628b 100644
--- a/Source/aweXpect.Core/Core/StringDifference.cs
+++ b/Source/aweXpect.Core/Core/StringDifference.cs
@@ -39,6 +39,22 @@ public enum MatchType
/// The expected string is treated as a regex pattern.
///
Regex,
+
+ ///
+ /// The expected string is treated as a prefix.
+ ///
+ ///
+ /// The actual string has to start with expected string.
+ ///
+ Prefix,
+
+ ///
+ /// The expected string is treated as a suffix.
+ ///
+ ///
+ /// The actual string has to end with expected string.
+ ///
+ Suffix,
}
private const string ActualIndicator = " (actual)";
@@ -56,7 +72,7 @@ public int IndexOfFirstMismatch(MatchType matchType)
return 0;
}
- _indexOfFirstMismatch ??= GetIndexOfFirstMismatch(actual, expected, _comparer);
+ _indexOfFirstMismatch ??= GetIndexOfFirstMismatch(actual, expected, _comparer, fromEnd: matchType is MatchType.Suffix);
return _indexOfFirstMismatch.Value;
}
@@ -102,7 +118,8 @@ public string ToString(string prefix)
{
MatchType.Wildcard => ToPatternString(MatchType.Wildcard, prefix, actual, expected),
MatchType.Regex => ToPatternString(MatchType.Regex, prefix, actual, expected),
- _ => ToEqualityString(prefix, actual, expected, IndexOfFirstMismatch(MatchType.Equality), settings),
+ _ => ToEqualityString(prefix, actual, expected,
+ IndexOfFirstMismatch(settings?.MatchType ?? MatchType.Equality), settings),
};
}
@@ -116,13 +133,24 @@ private static string ToEqualityString(string prefix, string actual, string expe
if (indexOfFirstMismatch < 0)
{
+ if (settings?.MatchType == MatchType.Suffix &&
+ actual.Length < expected.Length)
+ {
+ return $"""
+ is shorter than the expected length of {expected.Length} and misses the prefix:
+ "{expected[..^actual.Length].DisplayWhitespace()}"
+ """;
+ }
+
return prefix;
}
int column = settings?.IgnoredTrailingColumns ?? 0;
+ int indexFromEnd = actual.Length - indexOfFirstMismatch;
StringBuilder sb = new();
- int trimStart =
- GetStartIndexOfPhraseToShowBeforeTheMismatchingIndex(actual, indexOfFirstMismatch);
+ int trimStart = settings?.MatchType == MatchType.Suffix
+ ? GetStartIndexOfPhraseToShowBeforeTheMismatchingIndexFromEnd(actual, indexFromEnd)
+ : GetStartIndexOfPhraseToShowBeforeTheMismatchingIndex(actual, indexOfFirstMismatch);
int whiteSpaceCountBeforeArrow = indexOfFirstMismatch - trimStart + linePrefix.Length;
@@ -132,7 +160,7 @@ private static string ToEqualityString(string prefix, string actual, string expe
}
string visibleText = actual[trimStart..indexOfFirstMismatch];
- whiteSpaceCountBeforeArrow += visibleText.Count(c => c is '\r' or '\n');
+ whiteSpaceCountBeforeArrow += visibleText.Count(c => c is '\r' or '\n' or '\t');
string matchingString = actual[..indexOfFirstMismatch];
int lineNumber = matchingString.Count(c => c == '\n');
@@ -148,17 +176,42 @@ private static string ToEqualityString(string prefix, string actual, string expe
sb.Append(prefix).Append(" on line ").Append(lineNumber + 1).Append(" and column ")
.Append(column).AppendLine(":");
}
+ else if (settings?.MatchType == MatchType.Suffix)
+ {
+ sb.Append(prefix).Append(" before index ").Append(indexOfFirstMismatch + column).AppendLine(":");
+ }
else
{
sb.Append(prefix).Append(" at index ").Append(indexOfFirstMismatch + column).AppendLine(":");
}
- sb.Append(' ', whiteSpaceCountBeforeArrow).Append(arrowDown).AppendLine(ActualIndicator);
- AppendPrefixAndEscapedPhraseToShowWithEllipsisAndSuffix(sb, linePrefix, actual,
- trimStart, suffix);
- AppendPrefixAndEscapedPhraseToShowWithEllipsisAndSuffix(sb, linePrefix, expected,
- trimStart, suffix);
- sb.Append(' ', whiteSpaceCountBeforeArrow).Append(arrowUp).Append(GetExpected(settings?.MatchType));
+ if (settings?.MatchType == MatchType.Suffix)
+ {
+ int trimStartExpected =
+ GetStartIndexOfPhraseToShowBeforeTheMismatchingIndexFromEnd(expected, indexFromEnd);
+ string actualText = CreatePrefixAndEscapedPhraseToShowWithEllipsisAndSuffix(linePrefix, actual,
+ trimStart, indexFromEnd, suffix);
+ string expectedText = CreatePrefixAndEscapedPhraseToShowWithEllipsisAndSuffix(linePrefix, expected,
+ trimStartExpected, indexFromEnd, suffix);
+ int actualIndentation = Math.Max(0, expectedText.Length - actualText.Length);
+ sb.Append(' ', whiteSpaceCountBeforeArrow + actualIndentation).Append(arrowDown)
+ .AppendLine(ActualIndicator);
+ sb.Append(' ', actualIndentation);
+ sb.Append(actualText);
+ sb.Append(' ', Math.Max(0, actualText.Length - expectedText.Length));
+ sb.Append(expectedText);
+ sb.Append(' ', whiteSpaceCountBeforeArrow + actualIndentation).Append(arrowUp)
+ .Append(GetExpected(settings.MatchType));
+ }
+ else
+ {
+ sb.Append(' ', whiteSpaceCountBeforeArrow).Append(arrowDown).AppendLine(ActualIndicator);
+ AppendPrefixAndEscapedPhraseToShowWithEllipsisAndSuffix(sb, linePrefix, actual,
+ trimStart, suffix);
+ AppendPrefixAndEscapedPhraseToShowWithEllipsisAndSuffix(sb, linePrefix, expected,
+ trimStart, suffix);
+ sb.Append(' ', whiteSpaceCountBeforeArrow).Append(arrowUp).Append(GetExpected(settings?.MatchType));
+ }
return sb.ToString();
}
@@ -215,8 +268,44 @@ private static void AppendPrefixAndEscapedPhraseToShowWithEllipsisAndSuffix(
stringBuilder.AppendLine(suffix);
}
+ ///
+ /// Creates the escaped visible phrase decorated with ellipsis, with
+ /// the and the .
+ ///
+ ///
+ /// When text phrase starts at and with a calculated length omits text
+ /// on start or end, an ellipsis is added.
+ ///
+ private static string CreatePrefixAndEscapedPhraseToShowWithEllipsisAndSuffix(string prefix, string text,
+ int indexOfStartingPhrase, int indexFromEnd, string suffix)
+ {
+ StringBuilder? stringBuilder = new();
+ int indexOfFirstMismatch = text.Length - indexFromEnd;
+ int minLength = indexOfFirstMismatch + 10 - indexOfStartingPhrase;
+ int subjectLength = GetLengthOfPhraseToShowOrDefaultLength(text[indexOfStartingPhrase..], minLength);
+ const char ellipsis = '\u2026';
+
+ stringBuilder.Append(prefix);
+
+ if (indexOfStartingPhrase > 0)
+ {
+ stringBuilder.Append(ellipsis);
+ }
+
+ stringBuilder.Append(text
+ .Substring(indexOfStartingPhrase, subjectLength).DisplayWhitespace().ToSingleLine());
+
+ if (text.Length > indexOfStartingPhrase + subjectLength)
+ {
+ stringBuilder.Append(ellipsis);
+ }
+
+ stringBuilder.AppendLine(suffix);
+ return stringBuilder.ToString();
+ }
+
private static int GetIndexOfFirstMismatch(string? actualValue, string? expectedValue,
- IEqualityComparer comparer)
+ IEqualityComparer comparer, bool fromEnd = false)
{
if (comparer.Equals(actualValue, expectedValue))
{
@@ -239,7 +328,9 @@ private static int GetIndexOfFirstMismatch(string? actualValue, string? expected
break;
}
- if (comparer.Equals(actualValue[..mid], expectedValue[..mid]))
+ if (fromEnd
+ ? comparer.Equals(actualValue[^mid..], expectedValue[^mid..])
+ : comparer.Equals(actualValue[..mid], expectedValue[..mid]))
{
min = mid;
}
@@ -249,7 +340,7 @@ private static int GetIndexOfFirstMismatch(string? actualValue, string? expected
}
}
- return min;
+ return fromEnd ? actualValue.Length - min - 1 : min;
}
///
@@ -258,11 +349,11 @@ private static int GetIndexOfFirstMismatch(string? actualValue, string? expected
///
/// If a word end is found between 45 and 60 characters, use this word end, otherwise keep 50 characters.
///
- private static int GetLengthOfPhraseToShowOrDefaultLength(string value)
+ private static int GetLengthOfPhraseToShowOrDefaultLength(string value, int? minLength = null)
{
- int minLength = Customize.aweXpect.Formatting().MinimumNumberOfCharactersAfterStringDifference.Get();
- int defaultLength = minLength + 5;
- int maxLength = minLength + 15;
+ minLength ??= Customize.aweXpect.Formatting().MinimumNumberOfCharactersAfterStringDifference.Get();
+ int defaultLength = minLength.Value + 5;
+ int maxLength = minLength.Value + 15;
const int lengthOfWhitespace = 1;
int indexOfWordBoundary = value
@@ -315,11 +406,54 @@ private static int GetStartIndexOfPhraseToShowBeforeTheMismatchingIndex(string v
return indexOfFirstMismatch - defaultCharactersToKeep;
}
+ ///
+ /// Calculates the start index of the visible segment from when highlighting the difference
+ /// at from the end.
+ ///
+ ///
+ /// Either keep the last 10 characters before from the end or a word begin (separated
+ /// by
+ /// whitespace) between 15 and 5 characters before from the end.
+ ///
+ private static int GetStartIndexOfPhraseToShowBeforeTheMismatchingIndexFromEnd(string value,
+ int indexFromEnd)
+ {
+ int minLength = Customize.aweXpect.Formatting().MinimumNumberOfCharactersAfterStringDifference.Get();
+ int defaultLength = minLength + 5;
+ int maxLength = minLength + 15;
+ const int lengthOfWhitespace = 1;
+ int phraseLengthToCheckForWordBoundary =
+ maxLength - minLength + lengthOfWhitespace;
+
+ int indexOfFirstMismatch = value.Length - indexFromEnd;
+ if (indexOfFirstMismatch <= defaultLength)
+ {
+ return 0;
+ }
+
+ int indexToStartSearchingForWordBoundary =
+ Math.Max(indexOfFirstMismatch - (maxLength + lengthOfWhitespace), 0);
+
+ int indexOfWordBoundary = value
+ .IndexOf(' ', indexToStartSearchingForWordBoundary,
+ phraseLengthToCheckForWordBoundary) -
+ indexToStartSearchingForWordBoundary;
+
+ if (indexOfWordBoundary >= 0)
+ {
+ return indexToStartSearchingForWordBoundary + indexOfWordBoundary + lengthOfWhitespace;
+ }
+
+ return indexOfFirstMismatch - defaultLength;
+ }
+
private static string GetExpected(MatchType? matchType)
=> matchType switch
{
MatchType.Wildcard => " (wildcard pattern)",
MatchType.Regex => " (regex pattern)",
+ MatchType.Prefix => " (expected prefix)",
+ MatchType.Suffix => " (expected suffix)",
_ => " (expected)",
};
}
diff --git a/Source/aweXpect.Core/Options/StringEqualityOptions.PrefixMatchType.cs b/Source/aweXpect.Core/Options/StringEqualityOptions.PrefixMatchType.cs
index da7ac23d0..b2e8dcc24 100644
--- a/Source/aweXpect.Core/Options/StringEqualityOptions.PrefixMatchType.cs
+++ b/Source/aweXpect.Core/Options/StringEqualityOptions.PrefixMatchType.cs
@@ -53,12 +53,18 @@ public string GetExtendedFailure(string it, string? actual, string? expected,
string prefix =
$"{it} was {Formatter.Format(actual.TruncateWithEllipsisOnWord(DefaultMaxLength).ToSingleLine())}";
- StringDifference stringDifference = new(actual, expected, comparer, settings);
- int indexOfFirstMismatch = stringDifference.IndexOfFirstMismatch(StringDifference.MatchType.Equality);
- if (indexOfFirstMismatch == 0 && comparer.Equals(actual.TrimStart(), expected))
+ StringDifference stringDifference = new(actual, expected, comparer,
+ settings.WithMatchType(StringDifference.MatchType.Prefix));
+ int indexOfFirstMismatch = stringDifference.IndexOfFirstMismatch(StringDifference.MatchType.Prefix);
+ if (indexOfFirstMismatch == 0)
{
- return
- $"{prefix} which has unexpected whitespace (\"{actual.Substring(0, GetIndexOfFirstMatch(actual, expected, comparer)).DisplayWhitespace().TruncateWithEllipsis(100)}\" at the beginning)";
+ string? trimmedActual = actual.TrimStart();
+ int commonLength = Math.Min(trimmedActual.Length, expected.Length);
+ if (comparer.Equals(trimmedActual[..commonLength], expected[..commonLength]))
+ {
+ return
+ $"{prefix} which has unexpected whitespace (\"{actual.Substring(0, GetIndexOfFirstMatch(actual, expected, comparer)).DisplayWhitespace().TruncateWithEllipsis(100)}\" at the beginning)";
+ }
}
if (indexOfFirstMismatch == 0 && comparer.Equals(actual, expected.TrimStart()))
diff --git a/Source/aweXpect.Core/Options/StringEqualityOptions.SuffixMatchType.cs b/Source/aweXpect.Core/Options/StringEqualityOptions.SuffixMatchType.cs
index dad7e36d3..289080e5b 100644
--- a/Source/aweXpect.Core/Options/StringEqualityOptions.SuffixMatchType.cs
+++ b/Source/aweXpect.Core/Options/StringEqualityOptions.SuffixMatchType.cs
@@ -54,18 +54,24 @@ public string GetExtendedFailure(string it, string? actual, string? expected,
string prefix =
$"{it} was {Formatter.Format(actual.TruncateWithEllipsisOnWord(DefaultMaxLength).ToSingleLine())}";
int minCommonLength = Math.Min(actual.Length, expected.Length);
- StringDifference stringDifference = new(actual, expected, comparer, settings);
- int indexOfFirstMismatch = stringDifference.IndexOfFirstMismatch(StringDifference.MatchType.Equality);
+ StringDifference stringDifference = new(actual, expected, comparer,
+ settings.WithMatchType(StringDifference.MatchType.Suffix));
+ int indexOfFirstMismatch = stringDifference.IndexOfFirstMismatch(StringDifference.MatchType.Suffix);
if (indexOfFirstMismatch == 0 && comparer.Equals(actual, expected.TrimStart()))
{
return
$"{prefix} which misses some whitespace (\"{expected.Substring(0, GetIndexOfFirstMatch(expected, actual, comparer)).DisplayWhitespace().TruncateWithEllipsis(100)}\" at the beginning)";
}
- if (indexOfFirstMismatch == minCommonLength && comparer.Equals(actual.TrimEnd(), expected))
+ if (indexOfFirstMismatch == actual.Length)
{
- return
- $"{prefix} which has unexpected whitespace (\"{actual.Substring(indexOfFirstMismatch).DisplayWhitespace().TruncateWithEllipsis(100)}\" at the end)";
+ string? trimmedActual = actual.TrimEnd();
+ int commonLength = Math.Min(trimmedActual.Length, expected.Length);
+ if (comparer.Equals(trimmedActual[^commonLength..], expected[^commonLength..]))
+ {
+ return
+ $"{prefix} which has unexpected whitespace (\"{actual.Substring(trimmedActual.Length).DisplayWhitespace().TruncateWithEllipsis(100)}\" at the end)";
+ }
}
if (indexOfFirstMismatch == minCommonLength && comparer.Equals(actual, expected.TrimEnd()))
diff --git a/Source/aweXpect/That/Strings/ThatString.EndsWith.cs b/Source/aweXpect/That/Strings/ThatString.EndsWith.cs
index 459cb0854..b2ad57d9c 100644
--- a/Source/aweXpect/That/Strings/ThatString.EndsWith.cs
+++ b/Source/aweXpect/That/Strings/ThatString.EndsWith.cs
@@ -39,78 +39,4 @@ public static partial class ThatString
source,
options);
}
-
- private sealed class EndsWithConstraint(
- string it,
- ExpectationGrammars grammars,
- string? expected,
- StringEqualityOptions options)
- : ConstraintResult.WithNotNullValue(it, grammars),
- IAsyncConstraint
- {
- public async Task IsMetBy(string? actual, CancellationToken cancellationToken)
- {
- Actual = actual;
- if (expected is null)
- {
- Outcome = IsNegated ? Outcome.Success : Outcome.Failure;
- return this;
- }
-
- Outcome = expected.Length <= actual?.Length && await options.AreConsideredEqual(
- actual.Substring(actual.Length - expected.Length, expected.Length), expected)
- ? Outcome.Success
- : Outcome.Failure;
- return this;
- }
-
- protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
- {
- stringBuilder.Append("ends with ");
- Formatter.Format(stringBuilder, expected);
- stringBuilder.Append(options);
- }
-
- protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
- {
- if (expected is null)
- {
- Formatter.Format(stringBuilder, Actual);
- stringBuilder.Append(" cannot be validated against ");
- }
- else if (expected.Length > Actual?.Length)
- {
- stringBuilder.Append(It).Append(" was ");
- Formatter.Format(stringBuilder, Actual);
- stringBuilder.Append(" and with length ").Append(Actual?.Length)
- .Append(" is shorter than the expected length of ").Append(expected.Length);
- }
- else
- {
- stringBuilder.Append(It).Append(" was ");
- Formatter.Format(stringBuilder, Actual);
- }
- }
-
- protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
- {
- stringBuilder.Append("does not end with ");
- Formatter.Format(stringBuilder, expected);
- stringBuilder.Append(options);
- }
-
- protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
- {
- if (expected is null)
- {
- Formatter.Format(stringBuilder, Actual);
- stringBuilder.Append(" cannot be validated against ");
- }
- else
- {
- stringBuilder.Append(It).Append(" was ");
- Formatter.Format(stringBuilder, Actual);
- }
- }
- }
}
diff --git a/Source/aweXpect/That/Strings/ThatString.StartsWith.cs b/Source/aweXpect/That/Strings/ThatString.StartsWith.cs
index c4304f3a4..5429e7f22 100644
--- a/Source/aweXpect/That/Strings/ThatString.StartsWith.cs
+++ b/Source/aweXpect/That/Strings/ThatString.StartsWith.cs
@@ -39,78 +39,4 @@ public static partial class ThatString
source,
options);
}
-
- private sealed class StartsWithConstraint(
- string it,
- ExpectationGrammars grammars,
- string? expected,
- StringEqualityOptions options)
- : ConstraintResult.WithNotNullValue(it, grammars),
- IAsyncConstraint
- {
- public async Task IsMetBy(string? actual, CancellationToken cancellationToken)
- {
- Actual = actual;
- if (expected is null)
- {
- Outcome = IsNegated ? Outcome.Success : Outcome.Failure;
- return this;
- }
-
- Outcome = expected.Length <= actual?.Length &&
- await options.AreConsideredEqual(actual[..expected.Length], expected)
- ? Outcome.Success
- : Outcome.Failure;
- return this;
- }
-
- protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
- {
- stringBuilder.Append("starts with ");
- Formatter.Format(stringBuilder, expected);
- stringBuilder.Append(options);
- }
-
- protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
- {
- if (expected is null)
- {
- Formatter.Format(stringBuilder, Actual);
- stringBuilder.Append(" cannot be validated against ");
- }
- else if (expected.Length > Actual?.Length)
- {
- stringBuilder.Append(It).Append(" was ");
- Formatter.Format(stringBuilder, Actual);
- stringBuilder.Append(" and with length ").Append(Actual?.Length)
- .Append(" is shorter than the expected length of ").Append(expected.Length);
- }
- else
- {
- stringBuilder.Append(It).Append(" was ");
- Formatter.Format(stringBuilder, Actual);
- }
- }
-
- protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
- {
- stringBuilder.Append("does not start with ");
- Formatter.Format(stringBuilder, expected);
- stringBuilder.Append(options);
- }
-
- protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
- {
- if (expected is null)
- {
- Formatter.Format(stringBuilder, Actual);
- stringBuilder.Append(" cannot be validated against ");
- }
- else
- {
- stringBuilder.Append(It).Append(" was ");
- Formatter.Format(stringBuilder, Actual);
- }
- }
- }
}
diff --git a/Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_net8.0.txt b/Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_net8.0.txt
index bac4cb137..febe3a1d1 100644
--- a/Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_net8.0.txt
+++ b/Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_net8.0.txt
@@ -305,6 +305,8 @@ namespace aweXpect.Core
Equality = 0,
Wildcard = 1,
Regex = 2,
+ Prefix = 3,
+ Suffix = 4,
}
}
public class StringDifferenceSettings
diff --git a/Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_netstandard2.0.txt b/Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_netstandard2.0.txt
index f2f2227d1..2a307520b 100644
--- a/Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_netstandard2.0.txt
+++ b/Tests/aweXpect.Core.Api.Tests/Expected/aweXpect.Core_netstandard2.0.txt
@@ -291,6 +291,8 @@ namespace aweXpect.Core
Equality = 0,
Wildcard = 1,
Regex = 2,
+ Prefix = 3,
+ Suffix = 4,
}
}
public class StringDifferenceSettings
diff --git a/Tests/aweXpect.Core.Tests/Core/StringDifferenceTests.cs b/Tests/aweXpect.Core.Tests/Core/StringDifferenceTests.cs
index ecb7ef8f0..15edf3477 100644
--- a/Tests/aweXpect.Core.Tests/Core/StringDifferenceTests.cs
+++ b/Tests/aweXpect.Core.Tests/Core/StringDifferenceTests.cs
@@ -6,21 +6,12 @@ namespace aweXpect.Core.Tests.Core;
public class StringDifferenceTests
{
[Theory]
- [InlineData(StringDifference.MatchType.Wildcard)]
- [InlineData(StringDifference.MatchType.Regex)]
- public async Task IndexOfFirstMismatch_ForWildcardOrRegex_ShouldBeZero(StringDifference.MatchType matchType)
- {
- const string actual = "Foo";
- const string expected = "Foo";
- StringDifference sut = new(actual, expected);
-
- int result = sut.IndexOfFirstMismatch(matchType);
-
- await That(result).IsEqualTo(0);
- }
-
- [Fact]
- public async Task ShouldCacheIndexOfFirstMismatch()
+ [InlineData(StringDifference.MatchType.Wildcard, 0)]
+ [InlineData(StringDifference.MatchType.Regex, 0)]
+ [InlineData(StringDifference.MatchType.Prefix, -1)]
+ [InlineData(StringDifference.MatchType.Suffix, -1)]
+ [InlineData(StringDifference.MatchType.Equality, -1)]
+ public async Task ShouldCacheIndexOfFirstMismatch(StringDifference.MatchType matchType, int expectedIndex)
{
const string actual = "Foo";
const string expected = "Foo";
@@ -28,318 +19,579 @@ public async Task ShouldCacheIndexOfFirstMismatch()
StringDifference sut = new(actual, expected, comparer);
- await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(-1);
- await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(-1);
+ await That(sut.IndexOfFirstMismatch(matchType)).IsEqualTo(expectedIndex);
+ await That(sut.IndexOfFirstMismatch(matchType)).IsEqualTo(expectedIndex);
}
- [Fact]
- public async Task WhenActualValueIsLongerThanExpected_ShouldDifferAtIndexActualLength()
+ public sealed class WildcardOrRegexTests
{
- const string actual = "A text that is longer";
- const string expected = "A text";
-
- StringDifference sut = new(actual, expected);
-
- await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(6);
- await That(sut.ToString()).IsEqualTo(
- """
- differs at index 6:
- ↓ (actual)
- "A text that is longer"
- "A text"
- ↑ (expected)
- """);
- }
+ [Theory]
+ [InlineData(StringDifference.MatchType.Wildcard, "wildcard pattern")]
+ [InlineData(StringDifference.MatchType.Regex, "regex pattern")]
+ public async Task WhenActualValueIsNull_ShouldUsePatternName(StringDifference.MatchType matchType,
+ string patternName)
+ {
+ const string? actual = null;
+ const string expected = "This is a text";
+
+ StringDifference sut = new(actual, expected,
+ settings: new StringDifferenceSettings(0, 0).WithMatchType(matchType));
+
+ await That(sut.ToString()).IsEqualTo(
+ $"""
+ differs:
+ ↓ (actual)
+
+ "This is a text"
+ ↑ ({patternName})
+ """);
+ }
- [Fact]
- public async Task WhenActualValueIsNull_ShouldDifferAtIndex0()
- {
- const string? actual = null;
- const string expected = "This is a text";
-
- StringDifference sut = new(actual, expected);
-
- await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(0);
- await That(sut.ToString()).IsEqualTo(
- """
- differs:
- ↓ (actual)
-
- "This is a text"
- ↑ (expected)
- """);
+ [Theory]
+ [InlineData(StringDifference.MatchType.Wildcard, "wildcard pattern")]
+ [InlineData(StringDifference.MatchType.Regex, "regex pattern")]
+ public async Task WhenExpectedValueIsNull_ShouldUsePatternName(StringDifference.MatchType matchType,
+ string patternName)
+ {
+ const string actual = "This is a text";
+ const string? expected = null;
+
+ StringDifference sut = new(actual, expected,
+ settings: new StringDifferenceSettings(0, 0).WithMatchType(matchType));
+
+ await That(sut.ToString()).IsEqualTo(
+ $"""
+ differs:
+ ↓ (actual)
+ "This is a text"
+
+ ↑ ({patternName})
+ """);
+ }
}
- [Theory]
- [InlineData(StringDifference.MatchType.Wildcard, "wildcard pattern")]
- [InlineData(StringDifference.MatchType.Regex, "regex pattern")]
- public async Task WhenActualValueIsNull_ShouldUsePatternName(StringDifference.MatchType matchType,
- string patternName)
+ public sealed class EqualityTests
{
- const string? actual = null;
- const string expected = "This is a text";
-
- StringDifference sut = new(actual, expected,
- settings: new StringDifferenceSettings(0, 0).WithMatchType(matchType));
-
- await That(sut.ToString()).IsEqualTo(
- $"""
- differs:
- ↓ (actual)
-
- "This is a text"
- ↑ ({patternName})
- """);
- }
+ [Fact]
+ public async Task WhenActualValueIsLongerThanExpected_ShouldDifferAtIndexActualLength()
+ {
+ const string actual = "A text that is longer";
+ const string expected = "A text";
+
+ StringDifference sut = new(actual, expected);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(6);
+ await That(sut.ToString()).IsEqualTo(
+ """
+ differs at index 6:
+ ↓ (actual)
+ "A text that is longer"
+ "A text"
+ ↑ (expected)
+ """);
+ }
- [Fact]
- public async Task WhenActualValueIsShorterThanExpected_ShouldDifferAtIndexExpectedLength()
- {
- const string actual = "A text";
- const string expected = "A text that is longer";
-
- StringDifference sut = new(actual, expected);
-
- await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(6);
- await That(sut.ToString()).IsEqualTo(
- """
- differs at index 6:
- ↓ (actual)
- "A text"
- "A text that is longer"
- ↑ (expected)
- """);
- }
+ [Fact]
+ public async Task WhenActualValueIsNull_ShouldDifferAtIndex0()
+ {
+ const string? actual = null;
+ const string expected = "This is a text";
+
+ StringDifference sut = new(actual, expected);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(0);
+ await That(sut.ToString()).IsEqualTo(
+ """
+ differs:
+ ↓ (actual)
+
+ "This is a text"
+ ↑ (expected)
+ """);
+ }
- [Fact]
- public async Task WhenExpectedValueIsNull_ShouldDifferAtIndex0()
- {
- const string actual = "This is a text";
- const string? expected = null;
-
- StringDifference sut = new(actual, expected);
-
- await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(0);
- await That(sut.ToString()).IsEqualTo(
- """
- differs:
- ↓ (actual)
- "This is a text"
-
- ↑ (expected)
- """);
- }
+ [Fact]
+ public async Task WhenActualValueIsShorterThanExpected_ShouldDifferAtIndexExpectedLength()
+ {
+ const string actual = "A text";
+ const string expected = "A text that is longer";
+
+ StringDifference sut = new(actual, expected);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(6);
+ await That(sut.ToString()).IsEqualTo(
+ """
+ differs at index 6:
+ ↓ (actual)
+ "A text"
+ "A text that is longer"
+ ↑ (expected)
+ """);
+ }
- [Theory]
- [InlineData(StringDifference.MatchType.Wildcard, "wildcard pattern")]
- [InlineData(StringDifference.MatchType.Regex, "regex pattern")]
- public async Task WhenExpectedValueIsNull_ShouldUsePatternName(StringDifference.MatchType matchType,
- string patternName)
- {
- const string actual = "This is a text";
- const string? expected = null;
-
- StringDifference sut = new(actual, expected,
- settings: new StringDifferenceSettings(0, 0).WithMatchType(matchType));
-
- await That(sut.ToString()).IsEqualTo(
- $"""
- differs:
- ↓ (actual)
- "This is a text"
-
- ↑ ({patternName})
- """);
- }
+ [Fact]
+ public async Task WhenExpectedValueIsNull_ShouldDifferAtIndex0()
+ {
+ const string actual = "This is a text";
+ const string? expected = null;
+
+ StringDifference sut = new(actual, expected);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(0);
+ await That(sut.ToString()).IsEqualTo(
+ """
+ differs:
+ ↓ (actual)
+ "This is a text"
+
+ ↑ (expected)
+ """);
+ }
- [Fact]
- public async Task WhenFirstMismatchIsBelow11Characters_ShouldIncludeCompleteTextBeforeFirstMismatch()
- {
- const string actual = "This is a long text";
- const string expected = "This is a text that differs at index 10";
-
- StringDifference sut = new(actual, expected);
-
- await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(10);
- await That(sut.ToString()).IsEqualTo(
- """
- differs at index 10:
- ↓ (actual)
- "This is a long text"
- "This is a text that differs at index 10"
- ↑ (expected)
- """);
- }
+ [Fact]
+ public async Task WhenFirstMismatchIsBelow11Characters_ShouldIncludeCompleteTextBeforeFirstMismatch()
+ {
+ const string actual = "This is a long text";
+ const string expected = "This is a text that differs at index 10";
+
+ StringDifference sut = new(actual, expected);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(10);
+ await That(sut.ToString()).IsEqualTo(
+ """
+ differs at index 10:
+ ↓ (actual)
+ "This is a long text"
+ "This is a text that differs at index 10"
+ ↑ (expected)
+ """);
+ }
- [Fact]
- public async Task WhenLongTextDiffers_ShouldCalculateIndexOfFirstMismatch()
- {
- const string actual = "this is a long text that differs in between two words";
- const string expected = "this is a long text which differs in between two words";
-
- StringDifference sut = new(actual, expected);
-
- await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(20);
- await That(sut.ToString()).IsEqualTo(
- """
- differs at index 20:
- ↓ (actual)
- "…is a long text that differs in between two words"
- "…is a long text which differs in between two words"
- ↑ (expected)
- """);
- }
+ [Fact]
+ public async Task WhenLongTextDiffers_ShouldCalculateIndexOfFirstMismatch()
+ {
+ const string actual = "this is a long text that differs in between two words";
+ const string expected = "this is a long text which differs in between two words";
+
+ StringDifference sut = new(actual, expected);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(20);
+ await That(sut.ToString()).IsEqualTo(
+ """
+ differs at index 20:
+ ↓ (actual)
+ "…is a long text that differs in between two words"
+ "…is a long text which differs in between two words"
+ ↑ (expected)
+ """);
+ }
- [Fact]
- public async Task WhenNoLeadingWordBoundaryExistsBetween5And15Characters_ShouldFallbackTo50Characters()
- {
- const string actual = "This text '_contains' a long word between 5 and 15 characters before the first mismatch";
- const string expected =
- "This text '_contains' a loNg word between 5 and 15 characters before the first mismatch";
- StringDifference sut = new(actual, expected);
-
- string result = sut.ToString();
-
- await That(result).IsEqualTo(
- """
- differs at index 26:
- ↓ (actual)
- "…ains' a long word between 5 and 15 characters before the…"
- "…ains' a loNg word between 5 and 15 characters before the…"
- ↑ (expected)
- """);
- }
+ [Fact]
+ public async Task WhenNoLeadingWordBoundaryExistsBetween5And15Characters_ShouldFallbackTo10Characters()
+ {
+ const string actual =
+ "This text '_contains' a long word between 5 and 15 characters before the first mismatch";
+ const string expected =
+ "This text '_contains' a loNg word between 5 and 15 characters before the first mismatch";
+ StringDifference sut = new(actual, expected);
+
+ string result = sut.ToString();
+
+ await That(result).IsEqualTo(
+ """
+ differs at index 26:
+ ↓ (actual)
+ "…ains' a long word between 5 and 15 characters before the…"
+ "…ains' a loNg word between 5 and 15 characters before the…"
+ ↑ (expected)
+ """);
+ }
- [Fact]
- public async Task WhenNoTrailingWordBoundaryExistsBetween45And60Characters_ShouldFallbackTo50Characters()
- {
- const string actual = "This text contains lot of words and is used for testing the WordBoundaryAlgorithm";
- const string expected =
- "This text is used to verify when between 45 and60characters_no word boundary exists";
- StringDifference sut = new(actual, expected);
-
- string result = sut.ToString();
-
- await That(result).IsEqualTo(
- """
- differs at index 10:
- ↓ (actual)
- "This text contains lot of words and is used for testing the…"
- "This text is used to verify when between 45 and6…"
- ↑ (expected)
- """);
- }
+ [Fact]
+ public async Task WhenNoTrailingWordBoundaryExistsBetween45And60Characters_ShouldFallbackTo50Characters()
+ {
+ const string actual = "This text contains lot of words and is used for testing the WordBoundaryAlgorithm";
+ const string expected =
+ "This text is used to verify when between 45 and60characters_no word boundary exists";
+ StringDifference sut = new(actual, expected);
+
+ string result = sut.ToString();
+
+ await That(result).IsEqualTo(
+ """
+ differs at index 10:
+ ↓ (actual)
+ "This text contains lot of words and is used for testing the…"
+ "This text is used to verify when between 45 and6…"
+ ↑ (expected)
+ """);
+ }
- [Fact]
- public async Task WhenStringContainsWhitespace_ShouldPositionArrowsCorrectly()
- {
- const string actual = "foo\rbar\nBAZ";
- const string expected = "foo\rbar\nbaz";
-
- StringDifference sut = new(actual, expected);
-
- await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(8);
- await That(sut.ToString()).IsEqualTo(
- """
- differs on line 2 and column 1:
- ↓ (actual)
- "foo\rbar\nBAZ"
- "foo\rbar\nbaz"
- ↑ (expected)
- """);
- }
+ [Fact]
+ public async Task WhenStringContainsWhitespace_ShouldPositionArrowsCorrectly()
+ {
+ const string actual = "foo\rbar\nBAZ";
+ const string expected = "foo\rbar\nbaz";
+
+ StringDifference sut = new(actual, expected);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(8);
+ await That(sut.ToString()).IsEqualTo(
+ """
+ differs on line 2 and column 1:
+ ↓ (actual)
+ "foo\rbar\nBAZ"
+ "foo\rbar\nbaz"
+ ↑ (expected)
+ """);
+ }
- [Fact]
- public async Task WhenStringsDifferInCaseOnly_ShouldDefaultToCaseSensitiveComparison()
- {
- const string actual = "this IS a text that only differs in casing";
- const string expected = "this is a text that only differs in casing";
-
- StringDifference sut = new(actual, expected);
-
- await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(5);
- await That(sut.ToString()).IsEqualTo(
- """
- differs at index 5:
- ↓ (actual)
- "this IS a text that only differs in casing"
- "this is a text that only differs in casing"
- ↑ (expected)
- """);
- }
+ [Fact]
+ public async Task WhenStringsDifferInCaseOnly_ShouldDefaultToCaseSensitiveComparison()
+ {
+ const string actual = "this IS a text that only differs in casing";
+ const string expected = "this is a text that only differs in casing";
+
+ StringDifference sut = new(actual, expected);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(5);
+ await That(sut.ToString()).IsEqualTo(
+ """
+ differs at index 5:
+ ↓ (actual)
+ "this IS a text that only differs in casing"
+ "this is a text that only differs in casing"
+ ↑ (expected)
+ """);
+ }
- [Fact]
- public async Task WhenStringsDifferInCaseOnly_WhenUsingAComparer_ShouldCompareSubstringsWithComparer()
- {
- const string actual = "this IS a text that only differs in casing";
- const string expected = "this is a text that only differs in casing";
- StringComparer comparer = StringComparer.OrdinalIgnoreCase;
+ [Fact]
+ public async Task WhenStringsDifferInCaseOnly_WhenUsingAComparer_ShouldCompareSubstringsWithComparer()
+ {
+ const string actual = "this IS a text that only differs in casing";
+ const string expected = "this is a text that only differs in casing";
+ StringComparer comparer = StringComparer.OrdinalIgnoreCase;
- StringDifference sut = new(actual, expected, comparer);
+ StringDifference sut = new(actual, expected, comparer);
- await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(-1);
- await That(sut.ToString()).IsEqualTo("differs");
- }
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(-1);
+ await That(sut.ToString()).IsEqualTo("differs");
+ }
- [Theory]
- [InlineData("foo", "bar", 0)]
- [InlineData("foo", "false", 1)]
- [InlineData("bar", "ban", 2)]
- [InlineData("foobar", "foo-", 3)]
- public async Task WhenTextDiffers_ShouldCalculateIndexOfFirstMismatch(
- string actual, string expected, int expectedIndex)
- {
- StringDifference sut = new(actual, expected);
+ [Theory]
+ [InlineData("foo", "bar", 0)]
+ [InlineData("foo", "false", 1)]
+ [InlineData("bar", "ban", 2)]
+ [InlineData("foobar", "foo-", 3)]
+ public async Task WhenTextDiffers_ShouldCalculateIndexOfFirstMismatch(
+ string actual, string expected, int expectedIndex)
+ {
+ StringDifference sut = new(actual, expected);
- await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(expectedIndex);
- }
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(expectedIndex);
+ }
- [Fact]
- public async Task WhenTextHasMultipleLines_ShouldIncludeLineAndColumnNumbers()
- {
- int expectedIndex = 100 + (3 * Environment.NewLine.Length);
- string nl = Environment.NewLine.DisplayWhitespace();
-
- string actual = """
- @startuml
- Alice -> Bob : Authentication Request
- Bob --> Alice : Authentication Response
- Alice -> Bob : Another authentication Request
- Alice <-- Bob : Another authentication Response
- @enduml
- """;
-
- string expected = """
- @startuml
- Alice -> Bob : Authentication Request
- Bob --> Alice : Authentication Response
- Alice -> Bob : Invalid authentication Request
- Alice <-- Bob : Another authentication Response
- @enduml
- """;
-
- StringDifference sut = new(actual, expected);
-
- await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(expectedIndex);
- await That(sut.ToString()).IsEqualTo(
- $"""
- differs on line 4 and column 16:
- ↓ (actual)
- "…-> Bob : Another authentication Request{nl}Alice <-- Bob :…"
- "…-> Bob : Invalid authentication Request{nl}Alice <-- Bob :…"
- ↑ (expected)
- """);
+ [Fact]
+ public async Task WhenTextHasMultipleLines_ShouldIncludeLineAndColumnNumbers()
+ {
+ int expectedIndex = 100 + (3 * Environment.NewLine.Length);
+ string nl = Environment.NewLine.DisplayWhitespace();
+
+ string actual = """
+ @startuml
+ Alice -> Bob : Authentication Request
+ Bob --> Alice : Authentication Response
+ Alice -> Bob : Another authentication Request
+ Alice <-- Bob : Another authentication Response
+ @enduml
+ """;
+
+ string expected = """
+ @startuml
+ Alice -> Bob : Authentication Request
+ Bob --> Alice : Authentication Response
+ Alice -> Bob : Invalid authentication Request
+ Alice <-- Bob : Another authentication Response
+ @enduml
+ """;
+
+ StringDifference sut = new(actual, expected);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(expectedIndex);
+ await That(sut.ToString()).IsEqualTo(
+ $"""
+ differs on line 4 and column 16:
+ ↓ (actual)
+ "…-> Bob : Another authentication Request{nl}Alice <-- Bob :…"
+ "…-> Bob : Invalid authentication Request{nl}Alice <-- Bob :…"
+ ↑ (expected)
+ """);
+ }
+
+ [Fact]
+ public async Task WhenTextIsSame_ShouldSetIndexOfFirstMismatchToNegativeOne()
+ {
+ const string actual = "this is a text that does not differ";
+
+ StringDifference sut = new(actual, actual);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(-1);
+ await That(sut.ToString()).IsEqualTo("differs");
+ }
}
- [Fact]
- public async Task WhenTextIsSame_ShouldSetIndexOfFirstMismatchToNegativeOne()
+ public sealed class SuffixTests
{
- const string actual = "this is a text that does not differ";
+ [Fact]
+ public async Task WhenActualValueIsNull_ShouldDifferAtIndex0()
+ {
+ const string? actual = null;
+ const string expected = "This is a text";
+
+ StringDifference sut = new(actual, expected, null, Settings);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Suffix)).IsEqualTo(0);
+ await That(sut.ToString()).IsEqualTo(
+ """
+ differs:
+ ↓ (actual)
+
+ "This is a text"
+ ↑ (expected suffix)
+ """);
+ }
+
+ [Fact]
+ public async Task WhenActualValueIsShorterThanExpected_ShouldDifferAtIndexMinusOne()
+ {
+ const string actual = "that is longer";
+ const string expected = "A text that is longer";
+
+ StringDifference sut = new(actual, expected, null, Settings);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Suffix)).IsEqualTo(-1);
+ await That(sut.ToString()).IsEqualTo(
+ """
+ is shorter than the expected length of 21 and misses the prefix:
+ "A text "
+ """);
+ }
+
+ [Fact]
+ public async Task WhenExpectedValueIsNull_ShouldDifferAtIndex0()
+ {
+ const string actual = "This is a text";
+ const string? expected = null;
+
+ StringDifference sut = new(actual, expected, null, Settings);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Suffix)).IsEqualTo(0);
+ await That(sut.ToString()).IsEqualTo(
+ """
+ differs:
+ ↓ (actual)
+ "This is a text"
+
+ ↑ (expected suffix)
+ """);
+ }
+
+ [Fact]
+ public async Task WhenFirstMismatchIsBelow14Characters_ShouldIncludeCompleteTextBeforeFirstMismatch()
+ {
+ const string actual = "This is a long text";
+ const string expected = "This is a text";
+
+ StringDifference sut = new(actual, expected, null, Settings);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Suffix)).IsEqualTo(13);
+ await That(sut.ToString()).IsEqualTo(
+ """
+ differs before index 13:
+ ↓ (actual)
+ "This is a long text"
+ "This is a text"
+ ↑ (expected suffix)
+ """);
+ }
+
+ [Fact]
+ public async Task WhenLongTextDiffers_ShouldCalculateIndexOfFirstMismatch()
+ {
+ const string actual = "this is a long text that differs in between two words";
+ const string expected = "this is a long text which differs in between two words";
+
+ StringDifference sut = new(actual, expected, null, Settings);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Suffix)).IsEqualTo(23);
+ await That(sut.ToString()).IsEqualTo(
+ """
+ differs before index 23:
+ ↓ (actual)
+ "this is a long text that differs in between two…"
+ "this is a long text which differs in between two…"
+ ↑ (expected suffix)
+ """);
+ }
- StringDifference sut = new(actual, actual);
+ [Fact]
+ public async Task WhenNoLeadingWordBoundaryExistsBetween45And60Characters_ShouldFallbackTo50Characters()
+ {
+ const string actual = "This text contains lot of words and is used for testing the WordBoundaryAlgorithm";
+ const string expected =
+ "This text contains lot of words and is used for testing the end of the WordBoundaryAlgorithm";
+ StringDifference sut = new(actual, expected, null, Settings);
+
+ string result = sut.ToString();
+
+ await That(result).IsEqualTo(
+ """
+ differs before index 54:
+ ↓ (actual)
+ "…text contains lot of words and is used for testing the WordBound…"
+ "…text contains lot of words and is used for testing the end of the WordBound…"
+ ↑ (expected suffix)
+ """);
+ }
+
+ [Fact]
+ public async Task WhenNoTrailingWordBoundaryExistsBetween5And15Characters_ShouldFallbackTo14Characters()
+ {
+ const string actual =
+ "This text '_contains' a long word between 5 and 15 characters after tHe last mismatch";
+ const string expected =
+ "This text '_contains' a long word between 5 and 15 characters after the last mismatch";
+ StringDifference sut = new(actual, expected, null, Settings);
+
+ string result = sut.ToString();
+
+ await That(result).IsEqualTo(
+ """
+ differs before index 69:
+ ↓ (actual)
+ "…'_contains' a long word between 5 and 15 characters after tHe last mismatc…"
+ "…'_contains' a long word between 5 and 15 characters after the last mismatc…"
+ ↑ (expected suffix)
+ """);
+ }
+
+ [Fact]
+ public async Task WhenStringContainsWhitespace_ShouldPositionArrowsCorrectly()
+ {
+ const string actual = "foo\rbAr\nbaz";
+ const string expected = "foo\rbar\nbaz";
+
+ StringDifference sut = new(actual, expected, null, Settings);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(5);
+ await That(sut.ToString()).IsEqualTo(
+ """
+ differs on line 1 and column 6:
+ ↓ (actual)
+ "foo\rbAr\nbaz"
+ "foo\rbar\nbaz"
+ ↑ (expected suffix)
+ """);
+ }
- await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(-1);
- await That(sut.ToString()).IsEqualTo("differs");
+ [Fact]
+ public async Task WhenStringsDifferInCaseOnly_ShouldDefaultToCaseSensitiveComparison()
+ {
+ const string actual = "this IS a text that only differs in casing";
+ const string expected = "this is a text that only differs in casing";
+
+ StringDifference sut = new(actual, expected, null, Settings);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Equality)).IsEqualTo(5);
+ await That(sut.ToString()).IsEqualTo(
+ """
+ differs before index 5:
+ ↓ (actual)
+ "this IS a text that only…"
+ "this is a text that only…"
+ ↑ (expected suffix)
+ """);
+ }
+
+ [Fact]
+ public async Task WhenStringsDifferInCaseOnly_WhenUsingAComparer_ShouldCompareSubstringsWithComparer()
+ {
+ const string actual = "this IS a text that only differs in casing";
+ const string expected = "this is a text that only differs in casing";
+ StringComparer comparer = StringComparer.OrdinalIgnoreCase;
+
+ StringDifference sut = new(actual, expected, comparer, Settings);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Suffix)).IsEqualTo(-1);
+ await That(sut.ToString()).IsEqualTo("differs");
+ }
+
+ [Theory]
+ [InlineData("foo", "bar", 2)]
+ [InlineData("foo", "bro", 1)]
+ [InlineData("bar", "var", 0)]
+ [InlineData("foobar", "bazbar", 2)]
+ public async Task WhenTextDiffers_ShouldCalculateIndexOfFirstMismatch(
+ string actual, string expected, int expectedIndex)
+ {
+ StringDifference sut = new(actual, expected, null, Settings);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Suffix)).IsEqualTo(expectedIndex);
+ }
+
+ [Fact]
+ public async Task WhenTextHasMultipleLines_ShouldIncludeLineAndColumnNumbers()
+ {
+ int expectedIndex = 106 + (3 * Environment.NewLine.Length);
+ string nl = Environment.NewLine.DisplayWhitespace();
+ string nb = new(' ', nl.Length); // newline blanks
+
+ string actual = """
+ @startuml
+ Alice -> Bob : Authentication Request
+ Bob --> Alice : Authentication Response
+ Alice -> Bob : Another authentication Request
+ Alice <-- Bob : Another authentication Response
+ @enduml
+ """;
+
+ string expected = """
+ @startuml
+ Alice -> Bob : Authentication Request
+ Bob --> Alice : Authentication Response
+ Alice -> Bob : Invalid authentication Request
+ Alice <-- Bob : Another authentication Response
+ @enduml
+ """;
+
+ StringDifference sut = new(actual, expected, null, Settings);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Suffix)).IsEqualTo(expectedIndex);
+ await That(sut.ToString()).IsEqualTo(
+ $"""
+ differs on line 4 and column 22:
+ {nb} ↓ (actual)
+ "…--> Alice : Authentication Response{nl}Alice -> Bob : Another authentication…"
+ "…--> Alice : Authentication Response{nl}Alice -> Bob : Invalid authentication…"
+ {nb} ↑ (expected suffix)
+ """);
+ }
+
+ [Fact]
+ public async Task WhenTextIsSame_ShouldSetIndexOfFirstMismatchToNegativeOne()
+ {
+ const string actual = "this is a text that does not differ";
+
+ StringDifference sut = new(actual, actual, null, Settings);
+
+ await That(sut.IndexOfFirstMismatch(StringDifference.MatchType.Suffix)).IsEqualTo(-1);
+ await That(sut.ToString()).IsEqualTo("differs");
+ }
+
+ private static readonly StringDifferenceSettings Settings = new(0, 0)
+ {
+ MatchType = StringDifference.MatchType.Suffix,
+ };
}
private sealed class ExecuteOnceComparer : IEqualityComparer
diff --git a/Tests/aweXpect.Tests/Strings/ThatString.EndsWith.Tests.cs b/Tests/aweXpect.Tests/Strings/ThatString.EndsWith.Tests.cs
index 3a28481b7..bba440795 100644
--- a/Tests/aweXpect.Tests/Strings/ThatString.EndsWith.Tests.cs
+++ b/Tests/aweXpect.Tests/Strings/ThatString.EndsWith.Tests.cs
@@ -24,11 +24,11 @@ await That(Act).Throws()
.WithMessage("""
Expected that subject
ends with "Text",
- but it was "some arbitrary text" which differs at index 0:
- ↓ (actual)
+ but it was "some arbitrary text" which differs before index 15:
+ ↓ (actual)
"some arbitrary text"
- "Text"
- ↑ (expected)
+ "Text"
+ ↑ (expected suffix)
Actual:
some arbitrary text
@@ -49,11 +49,11 @@ await That(Act).Throws()
.WithMessage("""
Expected that subject
ends with "SOME" ignoring case,
- but it was "some arbitrary text" which differs at index 4:
- ↓ (actual)
+ but it was "some arbitrary text" which differs before index 18:
+ ↓ (actual)
"some arbitrary text"
- "SOME"
- ↑ (expected)
+ "SOME"
+ ↑ (expected suffix)
Actual:
some arbitrary text
@@ -65,7 +65,7 @@ public async Task
Using_WhenSubjectEndsWithIncorrectMatchAccordingToComparer_ShouldIncludeComparerInMessage()
{
string subject = "some arbitrary text";
- string expected = "TEXT";
+ string expected = "Text";
async Task Act()
=> await That(subject).EndsWith(expected)
@@ -74,13 +74,13 @@ async Task Act()
await That(Act).Throws()
.WithMessage("""
Expected that subject
- ends with "TEXT" using IgnoreCaseForVocalsComparer,
- but it was "some arbitrary text" which differs at index 0:
- ↓ (actual)
+ ends with "Text" using IgnoreCaseForVocalsComparer,
+ but it was "some arbitrary text" which differs before index 15:
+ ↓ (actual)
"some arbitrary text"
- "TEXT"
- ↑ (expected)
-
+ "Text"
+ ↑ (expected suffix)
+
Actual:
some arbitrary text
""");
@@ -114,7 +114,7 @@ await That(Act).Throws()
Expected that subject
ends with ,
but it was "text"
-
+
Actual:
text
""");
@@ -133,11 +133,11 @@ await That(Act).Throws()
.WithMessage("""
Expected that subject
ends with "some",
- but it was "some arbitrary text" which differs at index 4:
- ↓ (actual)
+ but it was "some arbitrary text" which differs before index 18:
+ ↓ (actual)
"some arbitrary text"
- "some"
- ↑ (expected)
+ "some"
+ ↑ (expected suffix)
Actual:
some arbitrary text
@@ -189,7 +189,7 @@ but it was
public async Task WhenSubjectIsShorterThanExpected_ShouldFail()
{
string subject = "text";
- string expected = "text and more";
+ string expected = "more than text";
async Task Act()
=> await That(subject).EndsWith(expected);
@@ -197,9 +197,9 @@ async Task Act()
await That(Act).Throws()
.WithMessage("""
Expected that subject
- ends with "text and more",
- but it was "text" with a length of 4 which is shorter than the expected length of 13 and misses:
- " and more"
+ ends with "more than text",
+ but it was "text" which is shorter than the expected length of 14 and misses the prefix:
+ "more than "
Actual:
text
diff --git a/Tests/aweXpect.Tests/Strings/ThatString.IsEqualTo.AsPrefixTests.cs b/Tests/aweXpect.Tests/Strings/ThatString.IsEqualTo.AsPrefixTests.cs
index 4d7070672..f249fc32e 100644
--- a/Tests/aweXpect.Tests/Strings/ThatString.IsEqualTo.AsPrefixTests.cs
+++ b/Tests/aweXpect.Tests/Strings/ThatString.IsEqualTo.AsPrefixTests.cs
@@ -111,7 +111,7 @@ some text
[Fact]
public async Task WhenStringHasUnexpectedLeadingWhitespace_ShouldFail()
{
- string subject = " \t some text";
+ string subject = " \t some text and more";
string expected = "some text";
async Task Act()
@@ -121,10 +121,10 @@ await That(Act).Throws()
.WithMessage("""
Expected that subject
starts with "some text",
- but it was " \t some text" which has unexpected whitespace (" \t " at the beginning)
+ but it was " \t some text and more" which has unexpected whitespace (" \t " at the beginning)
Actual:
- some text
+ some text and more
""");
}
@@ -178,7 +178,7 @@ Expected that subject
↓ (actual)
"actual text"
"expected other text"
- ↑ (expected)
+ ↑ (expected prefix)
Actual:
actual text
diff --git a/Tests/aweXpect.Tests/Strings/ThatString.IsEqualTo.AsSuffixTests.cs b/Tests/aweXpect.Tests/Strings/ThatString.IsEqualTo.AsSuffixTests.cs
index 6ecb53574..c0a75918b 100644
--- a/Tests/aweXpect.Tests/Strings/ThatString.IsEqualTo.AsSuffixTests.cs
+++ b/Tests/aweXpect.Tests/Strings/ThatString.IsEqualTo.AsSuffixTests.cs
@@ -92,8 +92,9 @@ await That(Act).Throws()
.WithMessage("""
Expected that subject
ends with " \t some text",
- but it was "some text" which misses some whitespace (" \t " at the beginning)
-
+ but it was "some text" which is shorter than the expected length of 12 and misses the prefix:
+ " \t "
+
Actual:
some text
""");
@@ -112,8 +113,12 @@ await That(Act).Throws()
.WithMessage("""
Expected that subject
ends with "some text \t ",
- but it was "some text" which misses some whitespace (" \t " at the end)
-
+ but it was "some text" which differs before index 8:
+ ↓ (actual)
+ "some text"
+ "some text \t "
+ ↑ (expected suffix)
+
Actual:
some text
""");
@@ -122,7 +127,7 @@ some text
[Fact]
public async Task WhenStringHasUnexpectedTrailingWhitespace_ShouldFail()
{
- string subject = "some text \t ";
+ string subject = "and some text \t ";
string expected = "some text";
async Task Act()
@@ -132,17 +137,21 @@ await That(Act).Throws()
.WithMessage("""
Expected that subject
ends with "some text",
- but it was "some text \t " which has unexpected whitespace (" \t " at the end)
-
+ but it was "and some text \t " which differs before index 15:
+ ↓ (actual)
+ "and some text \t "
+ "some text"
+ ↑ (expected suffix)
+
Actual:
- some text
+ and some text
""");
}
[Fact]
public async Task WhenStringIsShorter_ShouldFail()
{
- string subject = "some text with";
+ string subject = "text without out";
string expected = "some text without out";
async Task Act()
@@ -152,11 +161,11 @@ await That(Act).Throws()
.WithMessage("""
Expected that subject
ends with "some text without out",
- but it was "some text with" with a length of 14 which is shorter than the expected length of 21 and misses:
- "out out"
-
+ but it was "text without out" which is shorter than the expected length of 21 and misses the prefix:
+ "some "
+
Actual:
- some text with
+ text without out
""");
}
@@ -176,7 +185,7 @@ async Task Act()
public async Task WhenStringsDiffer_ShouldFail()
{
string subject = "actual text";
- string expected = "expected other text";
+ string expected = "other text";
async Task Act()
=> await That(subject).IsEqualTo(expected).AsSuffix();
@@ -184,13 +193,13 @@ async Task Act()
await That(Act).Throws()
.WithMessage("""
Expected that subject
- ends with "expected other text",
- but it was "actual text" which differs at index 0:
- ↓ (actual)
+ ends with "other text",
+ but it was "actual text" which differs before index 5:
+ ↓ (actual)
"actual text"
- "expected other text"
- ↑ (expected)
-
+ "other text"
+ ↑ (expected suffix)
+
Actual:
actual text
""");
diff --git a/Tests/aweXpect.Tests/Strings/ThatString.StartsWith.Tests.cs b/Tests/aweXpect.Tests/Strings/ThatString.StartsWith.Tests.cs
index 597c27d05..155e2bb1b 100644
--- a/Tests/aweXpect.Tests/Strings/ThatString.StartsWith.Tests.cs
+++ b/Tests/aweXpect.Tests/Strings/ThatString.StartsWith.Tests.cs
@@ -29,8 +29,8 @@ Expected that subject
↓ (actual)
"Some arbitrary text"
"SOME"
- ↑ (expected)
-
+ ↑ (expected prefix)
+
Actual:
Some arbitrary text
""");
@@ -54,8 +54,8 @@ Expected that subject
↓ (actual)
"some arbitrary text"
"TEXT"
- ↑ (expected)
-
+ ↑ (expected prefix)
+
Actual:
some arbitrary text
""");
@@ -80,8 +80,8 @@ Expected that subject
↓ (actual)
"some arbitrary text"
"SOME"
- ↑ (expected)
-
+ ↑ (expected prefix)
+
Actual:
some arbitrary text
""");
@@ -115,7 +115,7 @@ await That(Act).Throws()
Expected that subject
starts with ,
but it was "text"
-
+
Actual:
text
""");
@@ -138,8 +138,8 @@ Expected that subject
↓ (actual)
"some arbitrary text"
"text"
- ↑ (expected)
-
+ ↑ (expected prefix)
+
Actual:
some arbitrary text
""");
@@ -189,7 +189,7 @@ Expected that subject
starts with "text and more",
but it was "text" with a length of 4 which is shorter than the expected length of 13 and misses:
" and more"
-
+
Actual:
text
""");