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/ExpectationBuilder.cs b/Source/aweXpect.Core/Core/ExpectationBuilder.cs index c5eb9bce8..f889cabea 100644 --- a/Source/aweXpect.Core/Core/ExpectationBuilder.cs +++ b/Source/aweXpect.Core/Core/ExpectationBuilder.cs @@ -44,7 +44,7 @@ public abstract class ExpectationBuilder protected ExpectationBuilder(string subjectExpression, ExpectationGrammars grammars = ExpectationGrammars.None) { AweXpectInitialization.EnsureInitialized(); - Subject = subjectExpression; + Subject = subjectExpression.TrimCommonWhiteSpace(); ExpectationGrammars = grammars; } diff --git a/Source/aweXpect.Core/Core/Helpers/StringExtensions.cs b/Source/aweXpect.Core/Core/Helpers/StringExtensions.cs index ec90a0a94..ff111981e 100644 --- a/Source/aweXpect.Core/Core/Helpers/StringExtensions.cs +++ b/Source/aweXpect.Core/Core/Helpers/StringExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Text; namespace aweXpect.Core.Helpers; @@ -90,4 +91,54 @@ public static string SubstringUntilFirst(this string name, char c) const char ellipsis = '\u2026'; return $"{value.Substring(0, indexOfWordBoundary)}{ellipsis}"; } + + public static string TrimCommonWhiteSpace(this string value) + { + string[] lines = value.Split('\n'); + if (lines.Length <= 1) + { + return value; + } + + StringBuilder sb = new(); + foreach (char c in lines[1]) + { + if (char.IsWhiteSpace(c)) + { + sb.Append(c); + } + else + { + break; + } + } + + string commonWhiteSpace = sb.ToString(); + + for (int l = 2; l < lines.Length; l++) + { + if (lines[l].StartsWith(commonWhiteSpace)) + { + continue; + } + + for (int i = 0; i < Math.Min(lines[l].Length, commonWhiteSpace.Length); i++) + { + if (lines[l][i] != commonWhiteSpace[i]) + { + commonWhiteSpace = commonWhiteSpace[..i]; + break; + } + } + } + + sb.Clear(); + sb.Append(lines[0]); + foreach (string? line in lines.Skip(1)) + { + sb.Append('\n').Append(line[commonWhiteSpace.Length..]); + } + + return sb.ToString(); + } } diff --git a/Tests/aweXpect.Core.Tests/Core/ExpectationBuilderTests.cs b/Tests/aweXpect.Core.Tests/Core/ExpectationBuilderTests.cs index 596339837..60891ac66 100644 --- a/Tests/aweXpect.Core.Tests/Core/ExpectationBuilderTests.cs +++ b/Tests/aweXpect.Core.Tests/Core/ExpectationBuilderTests.cs @@ -66,6 +66,29 @@ public async Task ForMember_WithSucceedingExpectation_ShouldReturnSuccessConstra await That(constraintResult.GetExpectationText()).IsEqualTo("length equal to 3"); } + [Fact] + public async Task WhenSubjectHasMultipleLines_ShouldTrimCommonWhiteSpace() + { + async Task Act() => await That(new[] + { + 1, 2, 3, + }).IsEmpty(); + + await That(Act).Throws() + .WithMessage(""" + Expected that new[] + { + 1, 2, 3, + } + is empty, + but it was [ + 1, + 2, + 3 + ] + """); + } + [Fact] public async Task WhenTypeImplementsIDescribableSubject_ShouldUseToStringFromIt() { diff --git a/Tests/aweXpect.Core.Tests/Core/Helpers/StringExtensionsTests.cs b/Tests/aweXpect.Core.Tests/Core/Helpers/StringExtensionsTests.cs index 8129d08a1..7089c9cc3 100644 --- a/Tests/aweXpect.Core.Tests/Core/Helpers/StringExtensionsTests.cs +++ b/Tests/aweXpect.Core.Tests/Core/Helpers/StringExtensionsTests.cs @@ -4,239 +4,353 @@ namespace aweXpect.Core.Tests.Core.Helpers; public class StringExtensionsTests { - [Fact] - public async Task DisplayWhitespace_ShouldEscapeNewlines() + public sealed class DisplayWhitespace { - string input = "\r,\n;\t "; - string expected = @"\r,\n;\t "; + [Fact] + public async Task ShouldEscapeNewlines() + { + string input = "\r,\n;\t "; + string expected = @"\r,\n;\t "; - string result = input.DisplayWhitespace(); + string result = input.DisplayWhitespace(); - await That(result).IsEqualTo(expected); - } + await That(result).IsEqualTo(expected); + } - [Fact] - public async Task DisplayWhitespace_WhenNull_ShouldReturnNull() - { - string? input = null; + [Fact] + public async Task WhenNull_ShouldReturnNull() + { + string? input = null; - string? result = input.DisplayWhitespace(); + string? result = input.DisplayWhitespace(); - await That(result).IsNull(); + await That(result).IsNull(); + } } - - [Fact] - public async Task Indent_WhenIndentationIsEmpty_ShouldReturnInput() + public sealed class Indent { - string input = "foo\nbar"; + [Fact] + public async Task WhenIndentationIsEmpty_ShouldReturnInput() + { + string input = "foo\nbar"; - string result = input.Indent(""); + string result = input.Indent(""); - await That(result).IsEqualTo(input); - } + await That(result).IsEqualTo(input); + } - [Fact] - public async Task Indent_WhenIndentationIsNotEmpty_ShouldReturnIndentedInput() - { - string input = "foo\nbar"; - string expected = " foo\n bar"; + [Fact] + public async Task WhenIndentationIsNotEmpty_ShouldReturnIndentedInput() + { + string input = "foo\nbar"; + string expected = " foo\n bar"; - string result = input.Indent(" "); + string result = input.Indent(" "); - await That(result).IsEqualTo(expected); - } + await That(result).IsEqualTo(expected); + } - [Fact] - public async Task Indent_WhenIndentFirstLineIsFalse_ShouldOnlyIndentSubsequentLines() - { - string input = "foo\nbar"; - string expected = "foo\n bar"; + [Fact] + public async Task WhenIndentFirstLineIsFalse_ShouldOnlyIndentSubsequentLines() + { + string input = "foo\nbar"; + string expected = "foo\n bar"; - string result = input.Indent(" ", false); + string result = input.Indent(" ", false); - await That(result).IsEqualTo(expected); - } + await That(result).IsEqualTo(expected); + } - [Fact] - public async Task Indent_WhenNull_ShouldReturnNull() - { - string? input = null; + [Fact] + public async Task WhenNull_ShouldReturnNull() + { + string? input = null; - string? result = input.Indent(); + string? result = input.Indent(); - await That(result).IsNull(); + await That(result).IsNull(); + } } - [Theory] - [InlineData("", "a ")] - [InlineData("apple", "an apple")] - [InlineData("bee", "a bee")] - [InlineData("Exception", "an Exception")] - [InlineData("NotSupportedException", "a NotSupportedException")] - public async Task PrependAOrAn(string input, string expected) + public sealed class PrependAOrAn { - string result = input.PrependAOrAn(); - - await That(result).IsEqualTo(expected); + [Theory] + [InlineData("", "a ")] + [InlineData("apple", "an apple")] + [InlineData("bee", "a bee")] + [InlineData("Exception", "an Exception")] + [InlineData("NotSupportedException", "a NotSupportedException")] + public async Task ShouldReturnExpectedResult(string input, string expected) + { + string result = input.PrependAOrAn(); + + await That(result).IsEqualTo(expected); + } } - [Fact] - public async Task RemoveNewlineStyle_ShouldReplaceNewlinesWithSlashN() + public sealed class RemoveNewlineStyle { - string input = "\ra\r\nb\nc"; - string expected = "\na\nb\nc"; + [Fact] + public async Task ShouldReplaceNewlinesWithSlashN() + { + string input = "\ra\r\nb\nc"; + string expected = "\na\nb\nc"; - string result = input.RemoveNewlineStyle(); + string result = input.RemoveNewlineStyle(); - await That(result).IsEqualTo(expected); - } + await That(result).IsEqualTo(expected); + } - [Fact] - public async Task RemoveNewlineStyle_WhenNull_ShouldReturnNull() - { - string? input = null; + [Fact] + public async Task WhenNull_ShouldReturnNull() + { + string? input = null; - string? result = input.RemoveNewlineStyle(); + string? result = input.RemoveNewlineStyle(); - await That(result).IsNull(); + await That(result).IsNull(); + } } - [Fact] - public async Task SubstringUntilFirst_WhenFirstCharacter_ShouldReturnEmptyString() + public sealed class SubstringUntilFirst { - string input = "a,b,c"; + [Fact] + public async Task WhenFirstCharacter_ShouldReturnEmptyString() + { + string input = "a,b,c"; - string result = input.SubstringUntilFirst('a'); + string result = input.SubstringUntilFirst('a'); - await That(result).IsEqualTo(""); - } + await That(result).IsEqualTo(""); + } - [Fact] - public async Task SubstringUntilFirst_WhenNotPresent_ShouldReturnString() - { - string input = "foo"; + [Fact] + public async Task WhenNotPresent_ShouldReturnString() + { + string input = "foo"; - string result = input.SubstringUntilFirst('X'); + string result = input.SubstringUntilFirst('X'); - await That(result).IsEqualTo(input); - } + await That(result).IsEqualTo(input); + } - [Fact] - public async Task SubstringUntilFirst_WhenPresent_ShouldReturnSubstringUntilFirstOccurrence() - { - string input = "a,b,c"; + [Fact] + public async Task WhenPresent_ShouldReturnSubstringUntilFirstOccurrence() + { + string input = "a,b,c"; - string result = input.SubstringUntilFirst(','); + string result = input.SubstringUntilFirst(','); - await That(result).IsEqualTo("a"); + await That(result).IsEqualTo("a"); + } } - - [Fact] - public async Task ToSingleLine_ShouldEscapeNewlines() + public sealed class ToSingleLine { - string input = "\r,\n;\t "; - string expected = "\\r,\\n;\t "; + [Fact] + public async Task ShouldEscapeNewlines() + { + string input = "\r,\n;\t "; + string expected = "\\r,\\n;\t "; - string result = input.ToSingleLine(); + string result = input.ToSingleLine(); - await That(result).IsEqualTo(expected); - } + await That(result).IsEqualTo(expected); + } - [Fact] - public async Task ToSingleLine_WhenNull_ShouldReturnNull() - { - string? input = null; + [Fact] + public async Task WhenNull_ShouldReturnNull() + { + string? input = null; - string? result = input.ToSingleLine(); + string? result = input.ToSingleLine(); - await That(result).IsNull(); + await That(result).IsNull(); + } } - [Fact] - public async Task TruncateWithEllipsis_WhenLonger_ShouldTruncateWithEllipsis() + public sealed class TruncateWithEllipsis { - string input = "12345678910"; - string expected = "1234567891…"; + [Fact] + public async Task WhenLonger_ShouldTruncateWithEllipsis() + { + string input = "12345678910"; + string expected = "1234567891…"; - string result = input.TruncateWithEllipsis(10); + string result = input.TruncateWithEllipsis(10); - await That(result).IsEqualTo(expected); - } + await That(result).IsEqualTo(expected); + } - [Fact] - public async Task TruncateWithEllipsis_WhenNull_ShouldReturnNull() - { - string? input = null; + [Fact] + public async Task WhenNull_ShouldReturnNull() + { + string? input = null; - string? result = input.TruncateWithEllipsis(10); + string? result = input.TruncateWithEllipsis(10); - await That(result).IsNull(); - } + await That(result).IsNull(); + } - [Fact] - public async Task TruncateWithEllipsis_WhenShorter_ShouldReturnInput() - { - string input = "1234567890"; + [Fact] + public async Task WhenShorter_ShouldReturnInput() + { + string input = "1234567890"; - string result = input.TruncateWithEllipsis(10); + string result = input.TruncateWithEllipsis(10); - await That(result).IsEqualTo(input); + await That(result).IsEqualTo(input); + } } - [Fact] - public async Task TruncateWithEllipsisOnWord_WhenLongerWithoutWordBoundary_ShouldTruncateOnWordWithEllipsis() + public sealed class TruncateWithEllipsisOnWord { - string input = "some word boundary"; - string expected = "some word…"; + [Fact] + public async Task WhenLongerWithoutWordBoundary_ShouldTruncateOnWordWithEllipsis() + { + string input = "some word boundary"; + string expected = "some word…"; - string result = input.TruncateWithEllipsisOnWord(11); + string result = input.TruncateWithEllipsisOnWord(11); - await That(result).IsEqualTo(expected); - } + await That(result).IsEqualTo(expected); + } - [Fact] - public async Task TruncateWithEllipsisOnWord_WhenLongerWithoutWordBoundary_ShouldTruncateWithEllipsis() - { - string input = "12345678910"; - string expected = "1234567891…"; + [Fact] + public async Task WhenLongerWithoutWordBoundary_ShouldTruncateWithEllipsis() + { + string input = "12345678910"; + string expected = "1234567891…"; - string result = input.TruncateWithEllipsisOnWord(10); + string result = input.TruncateWithEllipsisOnWord(10); - await That(result).IsEqualTo(expected); - } + await That(result).IsEqualTo(expected); + } - [Fact] - public async Task TruncateWithEllipsisOnWord_WhenNull_ShouldReturnNull() - { - string? input = null; + [Fact] + public async Task WhenNull_ShouldReturnNull() + { + string? input = null; - string? result = input.TruncateWithEllipsisOnWord(10); + string? result = input.TruncateWithEllipsisOnWord(10); - await That(result).IsNull(); - } + await That(result).IsNull(); + } - [Fact] - public async Task TruncateWithEllipsisOnWord_WhenShorter_ShouldReturnInput() - { - string input = "1234567890"; + [Fact] + public async Task WhenShorter_ShouldReturnInput() + { + string input = "1234567890"; + + string result = input.TruncateWithEllipsisOnWord(10); - string result = input.TruncateWithEllipsisOnWord(10); + await That(result).IsEqualTo(input); + } - await That(result).IsEqualTo(input); + [Theory] + [InlineData("another word-boundary", "another wo…")] + [InlineData("_another word-boundary", "_another…")] + public async Task WhenWordBoundaryIsBelow80Percent_ShouldTruncateWithEllipsis( + string input, string expected) + { + string result = input.TruncateWithEllipsisOnWord(10); + + await That(result).IsEqualTo(expected); + } } - [Theory] - [InlineData("another word-boundary", "another wo…")] - [InlineData("_another word-boundary", "_another…")] - public async Task TruncateWithEllipsisOnWord_WhenWordBoundaryIsBelow80Percent_ShouldTruncateWithEllipsis( - string input, string expected) + public sealed class TrimCommonWhiteSpace { - string result = input.TruncateWithEllipsisOnWord(10); - - await That(result).IsEqualTo(expected); + [Fact] + public async Task WhenAnyLaterLineHasNoWhiteSpace_ShouldReturnUnchangedInput() + { + string input = """ + foo + bar + baz + bay + """; + + string result = input.TrimCommonWhiteSpace(); + + await That(result).IsEqualTo(input); + } + + [Fact] + public async Task WhenEmpty_ShouldReturnEmptyString() + { + string input = string.Empty; + + string result = input.TrimCommonWhiteSpace(); + + await That(result).IsEmpty(); + } + + [Fact] + public async Task WhenLinesHaveDifferentWhiteSpace_ShouldKeepAllWhiteSpace() + { + string input = """ + foo + bar + baz + """; + + string result = input.TrimCommonWhiteSpace(); + + await That(result).IsEqualTo(""" + foo + bar + baz + """); + } + + [Fact] + public async Task WhenLinesHaveSomeCommonWhiteSpace_ShouldTrim() + { + string input = """ + foo + bar + baz + bay + """; + + string result = input.TrimCommonWhiteSpace(); + + await That(result).IsEqualTo(""" + foo + bar + baz + bay + """); + } + + [Fact] + public async Task WhenOnlyHasOneLine_ShouldReturnLine() + { + string input = "foo"; + + string result = input.TrimCommonWhiteSpace(); + + await That(result).IsEqualTo(input); + } + + [Fact] + public async Task WhenTwoLines_ShouldTrimSecondLine() + { + string input = """ + foo + bar + """; + + string result = input.TrimCommonWhiteSpace(); + + await That(result).IsEqualTo(""" + foo + bar + """); + } } }