diff --git a/AGENTS.md b/AGENTS.md
index df13986f..a5672420 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -10,6 +10,10 @@ Plans typically have acceptance criteria with check boxes. Check each box when y
Read ./ai-plans/AGENTS.md for details on how to write plans.
+## Test Rules
+
+Read ./tests/AGENTS.md for details on how to write tests.
+
## Here is Your Space
If you encounter something worth noting while you are working on this code base, write it down here in this section. Once you are finished, I will discuss it with you, and we can decide where to put your notes.
diff --git a/Light.GuardClauses.slnx b/Light.GuardClauses.slnx
index 8f69c999..8e7cab72 100644
--- a/Light.GuardClauses.slnx
+++ b/Light.GuardClauses.slnx
@@ -15,9 +15,12 @@
+
+
+
diff --git a/tests/AGENTS.md b/tests/AGENTS.md
new file mode 100644
index 00000000..c68ee7b9
--- /dev/null
+++ b/tests/AGENTS.md
@@ -0,0 +1,13 @@
+# AGENTS.md for Testing
+
+## How to structure tests
+
+- Do not use mocking frameworks like Moq or NSubstitute for test doubles, use hand-written test doubles instead.
+- Use FluentAssertions instead of xunit's `Assert` class. The library is pinned to 7.x.x to avoid licensing issues.
+- Keep Test Coverage at least at 93%. Microsoft.Testing.Extensions.CodeCoverage is available to get test coverage metrics.
+- Prefer Sociable Tests as proposed by Martin Fowler. Only use Solitary Tests as a last resort.
+
+# How to run tests
+
+- `dotnet test` for usual test runs.
+- `dotnet test -- --coverage --coverage-output-format cobertura` for test coverage metrics.
diff --git a/tests/Light.GuardClauses.Tests/ComparableAssertions/NumericCustomFactorySuccessTests.cs b/tests/Light.GuardClauses.Tests/ComparableAssertions/NumericCustomFactorySuccessTests.cs
new file mode 100644
index 00000000..24271fb9
--- /dev/null
+++ b/tests/Light.GuardClauses.Tests/ComparableAssertions/NumericCustomFactorySuccessTests.cs
@@ -0,0 +1,71 @@
+using System;
+using FluentAssertions;
+using Xunit;
+
+namespace Light.GuardClauses.Tests.ComparableAssertions;
+
+public static class NumericCustomFactorySuccessTests
+{
+ [Fact]
+ public static void MustBePositiveReturnsValidValuesWithoutInvokingFactories()
+ {
+ 1.MustBePositive(_ => throw new InvalidOperationException()).Should().Be(1);
+ 2L.MustBePositive(_ => throw new InvalidOperationException()).Should().Be(2L);
+ 3m.MustBePositive(_ => throw new InvalidOperationException()).Should().Be(3m);
+ 4f.MustBePositive(_ => throw new InvalidOperationException()).Should().Be(4f);
+ 5d.MustBePositive(_ => throw new InvalidOperationException()).Should().Be(5d);
+ TimeSpan.FromTicks(6).MustBePositive(_ => throw new InvalidOperationException()).Should()
+ .Be(TimeSpan.FromTicks(6));
+ ((short) 7).MustBePositive(_ => throw new InvalidOperationException()).Should().Be(7);
+ }
+
+ [Fact]
+ public static void MustBeNegativeReturnsValidValuesWithoutInvokingFactories()
+ {
+ (-1).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-1);
+ (-2L).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-2L);
+ (-3m).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-3m);
+ (-4f).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-4f);
+ (-5d).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-5d);
+ TimeSpan.FromTicks(-6).MustBeNegative(_ => throw new InvalidOperationException()).Should()
+ .Be(TimeSpan.FromTicks(-6));
+ ((short) -7).MustBeNegative(_ => throw new InvalidOperationException()).Should().Be(-7);
+ }
+
+ [Fact]
+ public static void MustNotBePositiveReturnsValidValuesWithoutInvokingFactories()
+ {
+ 0.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0);
+ 0L.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0L);
+ 0m.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0m);
+ 0f.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0f);
+ 0d.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0d);
+ TimeSpan.Zero.MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(TimeSpan.Zero);
+ ((short) 0).MustNotBePositive(_ => throw new InvalidOperationException()).Should().Be(0);
+ }
+
+ [Fact]
+ public static void MustNotBeNegativeReturnsValidValuesWithoutInvokingFactories()
+ {
+ 0.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0);
+ 0L.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0L);
+ 0m.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0m);
+ 0f.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0f);
+ 0d.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0d);
+ TimeSpan.Zero.MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(TimeSpan.Zero);
+ ((short) 0).MustNotBeNegative(_ => throw new InvalidOperationException()).Should().Be(0);
+ }
+
+ [Fact]
+ public static void MustNotBeZeroReturnsValidValuesWithoutInvokingFactories()
+ {
+ 1.MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(1);
+ 2L.MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(2L);
+ 3m.MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(3m);
+ 4f.MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(4f);
+ 5d.MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(5d);
+ TimeSpan.FromTicks(6).MustNotBeZero(_ => throw new InvalidOperationException()).Should()
+ .Be(TimeSpan.FromTicks(6));
+ ((short) 7).MustNotBeZero(_ => throw new InvalidOperationException()).Should().Be(7);
+ }
+}
diff --git a/tests/Light.GuardClauses.Tests/FrameworkExtensions/EnumerableCountTests.cs b/tests/Light.GuardClauses.Tests/FrameworkExtensions/EnumerableCountTests.cs
new file mode 100644
index 00000000..15296511
--- /dev/null
+++ b/tests/Light.GuardClauses.Tests/FrameworkExtensions/EnumerableCountTests.cs
@@ -0,0 +1,55 @@
+using System.Collections;
+using System.Collections.Generic;
+using FluentAssertions;
+using Light.GuardClauses.FrameworkExtensions;
+using Xunit;
+
+namespace Light.GuardClauses.Tests.FrameworkExtensions;
+
+public static class EnumerableCountTests
+{
+ [Fact]
+ public static void CountWithExceptionDetailsUsesStringLengthWithoutEnumerating()
+ {
+ IEnumerable value = "text";
+
+ value.Count("value", "message").Should().Be(4);
+ }
+
+ [Fact]
+ public static void GenericCountWithExceptionDetailsUsesEveryAvailableStrategy()
+ {
+ IEnumerable text = "text";
+ var collection = new HashSet { 1, 2, 3 };
+ var readOnlyCollection = new ReadOnlyCollectionOnly([1, 2, 3, 4]);
+
+ text.GetCount("text", "message").Should().Be(4);
+ collection.GetCount("collection", "message").Should().Be(3);
+ readOnlyCollection.GetCount("readOnlyCollection", "message").Should().Be(4);
+ Yield(1, 2, 3, 4, 5).GetCount("items", "message").Should().Be(5);
+ }
+
+ [Fact]
+ public static void IsOneOfEnumeratesSourcesThatAreNotCollections()
+ {
+ 2.IsOneOf(Yield(1, 2, 3)).Should().BeTrue();
+ 4.IsOneOf(Yield(1, 2, 3)).Should().BeFalse();
+ }
+
+ private static IEnumerable Yield(params T[] items)
+ {
+ foreach (var item in items)
+ {
+ yield return item;
+ }
+ }
+
+ private sealed class ReadOnlyCollectionOnly(IReadOnlyCollection items) : IReadOnlyCollection
+ {
+ public int Count => items.Count;
+
+ public IEnumerator GetEnumerator() => items.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ }
+}
diff --git a/tests/Light.GuardClauses.Tests/FrameworkExtensions/MultiplyAddHashTests.cs b/tests/Light.GuardClauses.Tests/FrameworkExtensions/MultiplyAddHashTests.cs
index 8b735e66..ce85eb1d 100644
--- a/tests/Light.GuardClauses.Tests/FrameworkExtensions/MultiplyAddHashTests.cs
+++ b/tests/Light.GuardClauses.Tests/FrameworkExtensions/MultiplyAddHashTests.cs
@@ -40,7 +40,7 @@ public static void ThreeParameters(string value1, int value2, char value3)
[Theory]
[InlineData("Foo", 42, 'a', 87.73665)]
[InlineData("Bar", -177422, 'Y', -15.25)]
- [InlineData(null, 0, default(char), 0.0)]
+ [InlineData(null, 0, '\0', 0.0)]
public static void FourParameters(string value1, int value2, char value3, double value4)
{
var hashCode1 = MultiplyAddHash.CreateHashCode(value1, value2, value3, value4);
@@ -53,4 +53,164 @@ public static void FourParameters(string value1, int value2, char value3, double
hashCode1.Should().Be(hashCode2);
}
-}
\ No newline at end of file
+
+ [Fact]
+ public static void EverySupportedArityMatchesTheBuilder()
+ {
+ var values = new object[]
+ { 1, "two", 3m, '4', 5L, 6f, 7d, (short) 8, (byte) 9, 10u, 11ul, null, 13, 14, 15, 16 };
+
+ MultiplyAddHash.CreateHashCode(values[0], values[1], values[2], values[3], values[4])
+ .Should().Be(CreateExpectedHash(values, 5));
+ MultiplyAddHash.CreateHashCode(values[0], values[1], values[2], values[3], values[4], values[5])
+ .Should().Be(CreateExpectedHash(values, 6));
+ MultiplyAddHash.CreateHashCode(values[0], values[1], values[2], values[3], values[4], values[5], values[6])
+ .Should().Be(CreateExpectedHash(values, 7));
+ MultiplyAddHash.CreateHashCode(
+ values[0],
+ values[1],
+ values[2],
+ values[3],
+ values[4],
+ values[5],
+ values[6],
+ values[7]
+ )
+ .Should().Be(CreateExpectedHash(values, 8));
+ MultiplyAddHash.CreateHashCode(
+ values[0],
+ values[1],
+ values[2],
+ values[3],
+ values[4],
+ values[5],
+ values[6],
+ values[7],
+ values[8]
+ )
+ .Should().Be(CreateExpectedHash(values, 9));
+ MultiplyAddHash.CreateHashCode(
+ values[0],
+ values[1],
+ values[2],
+ values[3],
+ values[4],
+ values[5],
+ values[6],
+ values[7],
+ values[8],
+ values[9]
+ )
+ .Should().Be(CreateExpectedHash(values, 10));
+ MultiplyAddHash.CreateHashCode(
+ values[0],
+ values[1],
+ values[2],
+ values[3],
+ values[4],
+ values[5],
+ values[6],
+ values[7],
+ values[8],
+ values[9],
+ values[10]
+ )
+ .Should().Be(CreateExpectedHash(values, 11));
+ MultiplyAddHash.CreateHashCode(
+ values[0],
+ values[1],
+ values[2],
+ values[3],
+ values[4],
+ values[5],
+ values[6],
+ values[7],
+ values[8],
+ values[9],
+ values[10],
+ values[11]
+ )
+ .Should().Be(CreateExpectedHash(values, 12));
+ MultiplyAddHash.CreateHashCode(
+ values[0],
+ values[1],
+ values[2],
+ values[3],
+ values[4],
+ values[5],
+ values[6],
+ values[7],
+ values[8],
+ values[9],
+ values[10],
+ values[11],
+ values[12]
+ )
+ .Should().Be(CreateExpectedHash(values, 13));
+ MultiplyAddHash.CreateHashCode(
+ values[0],
+ values[1],
+ values[2],
+ values[3],
+ values[4],
+ values[5],
+ values[6],
+ values[7],
+ values[8],
+ values[9],
+ values[10],
+ values[11],
+ values[12],
+ values[13]
+ )
+ .Should().Be(CreateExpectedHash(values, 14));
+ MultiplyAddHash.CreateHashCode(
+ values[0],
+ values[1],
+ values[2],
+ values[3],
+ values[4],
+ values[5],
+ values[6],
+ values[7],
+ values[8],
+ values[9],
+ values[10],
+ values[11],
+ values[12],
+ values[13],
+ values[14]
+ )
+ .Should().Be(CreateExpectedHash(values, 15));
+ MultiplyAddHash.CreateHashCode(
+ values[0],
+ values[1],
+ values[2],
+ values[3],
+ values[4],
+ values[5],
+ values[6],
+ values[7],
+ values[8],
+ values[9],
+ values[10],
+ values[11],
+ values[12],
+ values[13],
+ values[14],
+ values[15]
+ )
+ .Should().Be(CreateExpectedHash(values, 16));
+ }
+
+ private static int CreateExpectedHash(object[] values, int count)
+ {
+ var builder = MultiplyAddHashBuilder.Create();
+ for (var i = 0; i < count; i++)
+ {
+ builder = builder.CombineIntoHash(values[i]);
+ }
+
+ return builder.BuildHash();
+ }
+}
diff --git a/tests/Light.GuardClauses.Tests/FrameworkExtensions/TextExtensionsTests.cs b/tests/Light.GuardClauses.Tests/FrameworkExtensions/TextExtensionsTests.cs
new file mode 100644
index 00000000..6f57e1a0
--- /dev/null
+++ b/tests/Light.GuardClauses.Tests/FrameworkExtensions/TextExtensionsTests.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Text;
+using FluentAssertions;
+using Light.GuardClauses.FrameworkExtensions;
+using Xunit;
+
+namespace Light.GuardClauses.Tests.FrameworkExtensions;
+
+public static class TextExtensionsTests
+{
+ [Fact]
+ public static void NestedExceptionMessagesAreAppendedInOrder()
+ {
+ var exception = new InvalidOperationException("outer", new ArgumentException("inner"));
+
+ var result = new StringBuilder().AppendExceptionMessages(exception);
+
+ result.ToString().Should().Be($"outer{Environment.NewLine}{Environment.NewLine}inner{Environment.NewLine}");
+ exception.GetAllExceptionMessages().Should().Be(result.ToString());
+ }
+
+ [Fact]
+ public static void EmptyTextDoesNotEqualNonEmptyTextWhenIgnoringWhiteSpace() =>
+ string.Empty.EqualsOrdinalIgnoreWhiteSpace("content").Should().BeFalse();
+}
diff --git a/tests/Light.GuardClauses.Tests/FrameworkExtensions/ToStringRepresentationTests.cs b/tests/Light.GuardClauses.Tests/FrameworkExtensions/ToStringRepresentationTests.cs
index 637402a5..121be2db 100644
--- a/tests/Light.GuardClauses.Tests/FrameworkExtensions/ToStringRepresentationTests.cs
+++ b/tests/Light.GuardClauses.Tests/FrameworkExtensions/ToStringRepresentationTests.cs
@@ -1,4 +1,5 @@
-using FluentAssertions;
+using System;
+using FluentAssertions;
using Light.GuardClauses.FrameworkExtensions;
using Xunit;
@@ -15,4 +16,44 @@ public static class ToStringRepresentationTests
[Theory]
[DefaultVariablesData]
public static void QuotedValues(string value) => value.ToStringRepresentation().Should().Be($"\"{value}\"");
-}
\ No newline at end of file
+
+ [Fact]
+ public static void EveryPrimitiveTypeConfiguredAsUnquotedIsNotQuoted()
+ {
+ 1L.ToStringRepresentation().Should().Be("1");
+ ((short) 2).ToStringRepresentation().Should().Be("2");
+ ((sbyte) 3).ToStringRepresentation().Should().Be("3");
+ 4u.ToStringRepresentation().Should().Be("4");
+ 5ul.ToStringRepresentation().Should().Be("5");
+ ((ushort) 6).ToStringRepresentation().Should().Be("6");
+ ((byte) 7).ToStringRepresentation().Should().Be("7");
+ true.ToStringRepresentation().Should().Be(bool.TrueString);
+ 8d.ToStringRepresentation().Should().Be("8");
+ 9m.ToStringRepresentation().Should().Be("9");
+ 10f.ToStringRepresentation().Should().Be("10");
+ }
+
+ [Fact]
+ public static void EmptyAndLongRepresentationsAreHandled()
+ {
+ new EmptyRepresentation().ToStringRepresentation().Should().BeEmpty();
+
+ var longText = new string('a', 127);
+ longText.ToStringRepresentation().Should().Be($"\"{longText}\"");
+ }
+
+ [Fact]
+ public static void NullValuesAreHandled()
+ {
+ string value = null;
+
+ value.ToStringOrNull("missing").Should().Be("missing");
+ ((Action) (() => value.ToStringRepresentation())).Should().Throw()
+ .WithParameterName(nameof(value));
+ }
+
+ private sealed class EmptyRepresentation
+ {
+ public override string ToString() => string.Empty;
+ }
+}
diff --git a/tests/Light.GuardClauses.Tests/StringAssertions/AsciiTests.cs b/tests/Light.GuardClauses.Tests/StringAssertions/AsciiTests.cs
index 6182c8ea..16dc666c 100644
--- a/tests/Light.GuardClauses.Tests/StringAssertions/AsciiTests.cs
+++ b/tests/Light.GuardClauses.Tests/StringAssertions/AsciiTests.cs
@@ -80,14 +80,14 @@ public static void ScalarAndStringFailuresSupportDefaultMessagesFactoriesAndNull
string nullText = null;
((Action) (() => invalidCharacter.MustBeAscii())).Should().ThrowExactly()
- .WithParameterName(nameof(invalidCharacter));
+ .WithParameterName(nameof(invalidCharacter));
((Action) (() => invalidByte.MustBeAscii(message: "custom"))).Should().ThrowExactly()
- .WithParameterName(nameof(invalidByte))
- .WithMessage("*custom*");
+ .WithParameterName(nameof(invalidByte))
+ .WithMessage("*custom*");
((Action) (() => invalidText.MustBeAscii())).Should().ThrowExactly()
- .WithParameterName(nameof(invalidText));
+ .WithParameterName(nameof(invalidText));
((Action) (() => nullText.MustBeAscii())).Should().Throw()
- .WithParameterName(nameof(nullText));
+ .WithParameterName(nameof(nullText));
Test.CustomException(invalidCharacter, (value, factory) => value.MustBeAscii(factory));
Test.CustomException(invalidByte, (value, factory) => value.MustBeAscii(factory));
Test.CustomException(nullText, (value, factory) => value.MustBeAscii(factory));
@@ -127,7 +127,7 @@ public static void BufferDefaultFailuresCaptureExpressionsAndMessages()
};
var readOnlySpanAct = () =>
{
- ReadOnlySpan invalidByteSpan = new byte[] { 128 };
+ ReadOnlySpan invalidByteSpan = [128];
invalidByteSpan.MustBeAscii(message: "custom");
};
var readOnlyCharacterSpanAct = () =>
@@ -142,30 +142,70 @@ public static void BufferDefaultFailuresCaptureExpressionsAndMessages()
spanAct.Should().ThrowExactly().WithParameterName("invalidCharacterSpan");
readOnlyCharacterSpanAct.Should().ThrowExactly()
.WithParameterName("invalidCharacterSpan");
- readOnlySpanAct.Should().ThrowExactly().WithParameterName("invalidByteSpan").WithMessage("*custom*");
+ readOnlySpanAct.Should().ThrowExactly().WithParameterName("invalidByteSpan")
+ .WithMessage("*custom*");
((Action) (() => invalidCharacterMemory.MustBeAscii())).Should().ThrowExactly()
- .WithParameterName(nameof(invalidCharacterMemory));
+ .WithParameterName(nameof(invalidCharacterMemory));
((Action) (() => invalidReadOnlyCharacterMemory.MustBeAscii())).Should().ThrowExactly()
- .WithParameterName(nameof(invalidReadOnlyCharacterMemory));
+ .WithParameterName(
+ nameof(invalidReadOnlyCharacterMemory)
+ );
((Action) (() => invalidByteMemory.MustBeAscii())).Should().ThrowExactly()
- .WithParameterName(nameof(invalidByteMemory));
+ .WithParameterName(nameof(invalidByteMemory));
}
[Fact]
public static void BufferFactoriesReceiveEveryShape()
{
Test.CustomSpanException("é".ToCharArray().AsSpan(), (value, factory) => value.MustBeAscii(factory));
- Test.CustomSpanException((ReadOnlySpan) "é".ToCharArray(),
- (value, factory) => value.MustBeAscii(factory));
+ Test.CustomSpanException(
+ (ReadOnlySpan) "é".ToCharArray(),
+ (value, factory) => value.MustBeAscii(factory)
+ );
Test.CustomMemoryException("é".ToCharArray().AsMemory(), (value, factory) => value.MustBeAscii(factory));
- Test.CustomMemoryException((ReadOnlyMemory) "é".ToCharArray(),
- (value, factory) => value.MustBeAscii(factory));
+ Test.CustomMemoryException(
+ (ReadOnlyMemory) "é".ToCharArray(),
+ (value, factory) => value.MustBeAscii(factory)
+ );
Test.CustomSpanException(new byte[] { 128 }.AsSpan(), (value, factory) => value.MustBeAscii(factory));
- Test.CustomSpanException((ReadOnlySpan) new byte[] { 128 },
- (value, factory) => value.MustBeAscii(factory));
+ Test.CustomSpanException(
+ (ReadOnlySpan) new byte[] { 128 },
+ (value, factory) => value.MustBeAscii(factory)
+ );
Test.CustomMemoryException(new byte[] { 128 }.AsMemory(), (value, factory) => value.MustBeAscii(factory));
- Test.CustomMemoryException((ReadOnlyMemory) new byte[] { 128 },
- (value, factory) => value.MustBeAscii(factory));
+ Test.CustomMemoryException(
+ (ReadOnlyMemory) new byte[] { 128 },
+ (value, factory) => value.MustBeAscii(factory)
+ );
+ }
+
+ [Fact]
+ public static void CustomFactoriesAreNotInvokedForAsciiValues()
+ {
+ var characters = "ASCII".ToCharArray();
+ var bytes = "ASCII"u8.ToArray();
+ var characterSpan = characters.AsSpan();
+ ReadOnlySpan readOnlyCharacterSpan = characters;
+ var byteSpan = bytes.AsSpan();
+ ReadOnlySpan readOnlyByteSpan = bytes;
+
+ 'A'.MustBeAscii(_ => throw new InvalidOperationException()).Should().Be('A');
+ ((byte) 127).MustBeAscii(_ => throw new InvalidOperationException()).Should().Be(127);
+ "ASCII".MustBeAscii(_ => throw new InvalidOperationException()).Should().Be("ASCII");
+ characterSpan.MustBeAscii(_ => throw new InvalidOperationException()).SequenceEqual(characterSpan).Should()
+ .BeTrue();
+ readOnlyCharacterSpan.MustBeAscii(_ => throw new InvalidOperationException())
+ .SequenceEqual(readOnlyCharacterSpan).Should().BeTrue();
+ characters.AsMemory().MustBeAscii(_ => throw new InvalidOperationException()).Should()
+ .Be(characters.AsMemory());
+ ((ReadOnlyMemory) characters).MustBeAscii(_ => throw new InvalidOperationException())
+ .Should().Be((ReadOnlyMemory) characters);
+ byteSpan.MustBeAscii(_ => throw new InvalidOperationException()).SequenceEqual(byteSpan).Should().BeTrue();
+ readOnlyByteSpan.MustBeAscii(_ => throw new InvalidOperationException()).SequenceEqual(readOnlyByteSpan)
+ .Should().BeTrue();
+ bytes.AsMemory().MustBeAscii(_ => throw new InvalidOperationException()).Should().Be(bytes.AsMemory());
+ ((ReadOnlyMemory) bytes).MustBeAscii(_ => throw new InvalidOperationException())
+ .Should().Be((ReadOnlyMemory) bytes);
}
}
diff --git a/tests/Light.GuardClauses.Tests/StringAssertions/MustBeEmailAddressTests.cs b/tests/Light.GuardClauses.Tests/StringAssertions/MustBeEmailAddressTests.cs
index 81c4c617..6251290a 100644
--- a/tests/Light.GuardClauses.Tests/StringAssertions/MustBeEmailAddressTests.cs
+++ b/tests/Light.GuardClauses.Tests/StringAssertions/MustBeEmailAddressTests.cs
@@ -29,7 +29,7 @@ public static void InvalidEmailAddress(string emailAddress)
[ClassData(typeof(InvalidEmailAddresses))]
public static void InvalidEmailAddressArgumentName(string emailAddress)
{
- Action act = () => emailAddress.MustBeEmailAddress(nameof(emailAddress));
+ Action act = () => emailAddress.MustBeEmailAddress();
act.Should().Throw()
.And.Message.Should().Contain(
@@ -43,7 +43,7 @@ public static void InvalidEmailAddressCustomMessage(string emailAddress)
{
const string customMessage = "This email address is not valid";
- Action act = () => emailAddress.MustBeEmailAddress(nameof(emailAddress), customMessage);
+ Action act = () => emailAddress.MustBeEmailAddress(message: customMessage);
act.Should().Throw()
.And.Message.Should().Contain(customMessage);
@@ -232,7 +232,7 @@ public static void InvalidEmailAddressArgumentName_Span(string email)
var act = () =>
{
var span = new Span(emailChars);
- span.MustBeEmailAddress(nameof(span));
+ span.MustBeEmailAddress();
};
act.Should().Throw()
.And.Message.Should().Contain("span");
@@ -247,7 +247,7 @@ public static void InvalidEmailAddressCustomMessage_Span(string email)
var act = () =>
{
var span = new Span(emailChars);
- span.MustBeEmailAddress(nameof(span), customMessage);
+ span.MustBeEmailAddress(message: customMessage);
};
act.Should().Throw()
.And.Message.Should().Contain(customMessage);
@@ -324,7 +324,7 @@ public static void InvalidEmailAddress_Memory(string email)
public static void InvalidEmailAddressArgumentName_Memory(string email)
{
var memory = email.ToCharArray().AsMemory();
- Action act = () => memory.MustBeEmailAddress(nameof(memory));
+ Action act = () => memory.MustBeEmailAddress();
act.Should().Throw()
.And.Message.Should().Contain(nameof(memory));
}
@@ -335,7 +335,7 @@ public static void InvalidEmailAddressCustomMessage_Memory(string email)
{
const string customMessage = "This email address is not valid";
var memory = email.ToCharArray().AsMemory();
- Action act = () => memory.MustBeEmailAddress(nameof(memory), customMessage);
+ Action act = () => memory.MustBeEmailAddress(message: customMessage);
act.Should().Throw()
.And.Message.Should().Contain(customMessage);
}
@@ -407,7 +407,7 @@ public static void InvalidEmailAddress_ReadOnlyMemory(string email)
public static void InvalidEmailAddressArgumentName_ReadOnlyMemory(string email)
{
var readOnlyMemory = email.AsMemory();
- Action act = () => readOnlyMemory.MustBeEmailAddress(nameof(readOnlyMemory));
+ Action act = () => readOnlyMemory.MustBeEmailAddress();
act.Should().Throw()
.And.Message.Should().Contain(nameof(readOnlyMemory));
}
@@ -418,7 +418,7 @@ public static void InvalidEmailAddressCustomMessage_ReadOnlyMemory(string email)
{
const string customMessage = "This email address is not valid";
var readOnlyMemory = email.AsMemory();
- Action act = () => readOnlyMemory.MustBeEmailAddress(nameof(readOnlyMemory), customMessage);
+ Action act = () => readOnlyMemory.MustBeEmailAddress(message: customMessage);
act.Should().Throw()
.And.Message.Should().Contain(customMessage);
}
@@ -464,5 +464,35 @@ public static void CustomMessageCustomRegex_ReadOnlyMemory()
message => readOnlyMemory.MustBeEmailAddress(CustomRegex, message: message)
);
}
+
+ [Fact]
+ public static void EveryBufferOverloadReturnsValidEmailAddressesWithoutInvokingFactories()
+ {
+ const string email = "user@example.com";
+ var characters = email.ToCharArray();
+ var span = characters.AsSpan();
+ ReadOnlySpan readOnlySpan = characters;
+ var memory = characters.AsMemory();
+ ReadOnlyMemory readOnlyMemory = characters;
+
+ readOnlySpan.MustBeEmailAddress(_ => throw new InvalidOperationException()).ToString().Should().Be(email);
+ readOnlySpan.MustBeEmailAddress(CustomRegex).ToString().Should().Be(email);
+ readOnlySpan.MustBeEmailAddress(CustomRegex, (_, _) => throw new InvalidOperationException())
+ .ToString().Should().Be(email);
+
+ span.MustBeEmailAddress(_ => throw new InvalidOperationException()).ToString().Should().Be(email);
+ span.MustBeEmailAddress(CustomRegex).ToString().Should().Be(email);
+ span.MustBeEmailAddress(CustomRegex, (_, _) => throw new InvalidOperationException()).ToString().Should()
+ .Be(email);
+
+ memory.MustBeEmailAddress(_ => throw new InvalidOperationException()).Should().Be(memory);
+ memory.MustBeEmailAddress(CustomRegex).Should().Be(memory);
+ memory.MustBeEmailAddress(CustomRegex, (_, _) => throw new InvalidOperationException()).Should().Be(memory);
+
+ readOnlyMemory.MustBeEmailAddress(_ => throw new InvalidOperationException()).Should().Be(readOnlyMemory);
+ readOnlyMemory.MustBeEmailAddress(CustomRegex).Should().Be(readOnlyMemory);
+ readOnlyMemory.MustBeEmailAddress(CustomRegex, (_, _) => throw new InvalidOperationException())
+ .Should().Be(readOnlyMemory);
+ }
#endif
}
diff --git a/tests/Light.GuardClauses.Tests/StringAssertions/MustNotStartWithTests.cs b/tests/Light.GuardClauses.Tests/StringAssertions/MustNotStartWithTests.cs
index 5ad5160c..9d74d758 100644
--- a/tests/Light.GuardClauses.Tests/StringAssertions/MustNotStartWithTests.cs
+++ b/tests/Light.GuardClauses.Tests/StringAssertions/MustNotStartWithTests.cs
@@ -21,7 +21,9 @@ public static void StartsWith(string x, string y)
var act = () => x.MustNotStartWith(y);
var exception = act.Should().Throw().Which;
- exception.Message.Should().StartWith($"{nameof(x)} must not start with \"{y}\" (CurrentCulture), but it actually is \"{x}\"");
+ exception.Message.Should().StartWith(
+ $"{nameof(x)} must not start with \"{y}\" (CurrentCulture), but it actually is \"{x}\""
+ );
exception.ParamName.Should().BeSameAs(nameof(x));
}
@@ -33,14 +35,16 @@ public static void StartsWithComparisonType(string x, string y, StringComparison
var act = () => x.MustNotStartWith(y, comparisonType);
var exception = act.Should().Throw().Which;
- exception.Message.Should().StartWith($"{nameof(x)} must not start with \"{y}\" ({comparisonType}), but it actually is \"{x}\"");
+ exception.Message.Should().StartWith(
+ $"{nameof(x)} must not start with \"{y}\" ({comparisonType}), but it actually is \"{x}\""
+ );
exception.ParamName.Should().BeSameAs(nameof(x));
}
[Fact]
public static void ParameterNull()
{
- var act = () => ((string)null).MustNotStartWith("Foo");
+ var act = () => ((string) null).MustNotStartWith("Foo");
act.Should().Throw();
}
@@ -59,15 +63,15 @@ public static void CustomMessage() =>
[Fact]
public static void CustomMessageParameterNull() =>
- Test.CustomMessage(message => ((string)null).MustNotStartWith("Bar", message: message));
+ Test.CustomMessage(message => ((string) null).MustNotStartWith("Bar", message: message));
[Fact]
public static void NotStartsWithCustomException() =>
- "Bar".MustNotStartWith("Foo", (_, _) => new Exception()).Should().Be("Bar");
+ "Bar".MustNotStartWith("Foo", (_, _) => new ()).Should().Be("Bar");
[Fact]
public static void NotStartsWithCustomExceptionAndComparisonType() =>
- "Bar".MustNotStartWith("foo", StringComparison.OrdinalIgnoreCase, (_, _, _) => new Exception()).Should().Be("Bar");
+ "Bar".MustNotStartWith("foo", StringComparison.OrdinalIgnoreCase, (_, _, _) => new ()).Should().Be("Bar");
[Theory]
[InlineData("Foo", "Foo")]
@@ -81,5 +85,47 @@ public static void CustomException(string first, string second) =>
[InlineData(null, "Bar", StringComparison.Ordinal)]
[InlineData("Baz", null, StringComparison.Ordinal)]
public static void CustomExceptionWithComparisonType(string a, string b, StringComparison comparisonType) =>
- Test.CustomException(a, b, comparisonType, (s1, s2, ct, exceptionFactory) => s1.MustNotStartWith(s2, ct, exceptionFactory));
+ Test.CustomException(
+ a,
+ b,
+ comparisonType,
+ (s1, s2, ct, exceptionFactory) => s1.MustNotStartWith(s2, ct, exceptionFactory)
+ );
+
+ [Fact]
+ public static void SpanOverloadsSupportSuccessAndFailurePaths()
+ {
+ ReadOnlySpan parameter = "value";
+ ReadOnlySpan other = "prefix";
+
+ parameter.MustNotStartWith(other, StringComparison.Ordinal).ToString().Should().Be("value");
+ parameter.MustNotStartWith(other, (_, _) => throw new InvalidOperationException()).ToString().Should()
+ .Be("value");
+ parameter.MustNotStartWith(other, StringComparison.Ordinal, (_, _, _) => throw new InvalidOperationException())
+ .ToString().Should().Be("value");
+
+ var defaultFailure = () =>
+ {
+ ReadOnlySpan invalid = "prefix-value";
+ invalid.MustNotStartWith("prefix", StringComparison.Ordinal);
+ };
+ var factoryFailure = () =>
+ {
+ ReadOnlySpan invalid = "prefix-value";
+ invalid.MustNotStartWith("prefix", (_, _) => new InvalidOperationException());
+ };
+ var comparisonFactoryFailure = () =>
+ {
+ ReadOnlySpan invalid = "PREFIX-value";
+ invalid.MustNotStartWith(
+ "prefix",
+ StringComparison.OrdinalIgnoreCase,
+ (_, _, _) => new InvalidOperationException()
+ );
+ };
+
+ defaultFailure.Should().Throw();
+ factoryFailure.Should().Throw();
+ comparisonFactoryFailure.Should().Throw();
+ }
}