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 """);