diff --git a/Source/aweXpect.Core/Core/SpanWrapper.cs b/Source/aweXpect.Core/Core/SpanWrapper.cs
new file mode 100644
index 000000000..f80fcab58
--- /dev/null
+++ b/Source/aweXpect.Core/Core/SpanWrapper.cs
@@ -0,0 +1,69 @@
+#if NET8_0_OR_GREATER
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace aweXpect.Core;
+
+///
+/// Wraps a span by providing access to the underlying data.
+///
+public class SpanWrapper : ICollection
+{
+ private readonly T[] _value;
+
+ ///
+ public SpanWrapper(ReadOnlySpan span)
+ {
+ _value = span.ToArray();
+ }
+
+ ///
+ public SpanWrapper(Span span)
+ {
+ _value = span.ToArray();
+ }
+
+ ///
+ /// Creates a new span over the wrapped array.
+ ///
+ public Span AsSpan() => _value.AsSpan();
+
+ ///
+ public IEnumerator GetEnumerator()
+ => (_value as IEnumerable).GetEnumerator();
+
+ ///
+ IEnumerator IEnumerable.GetEnumerator()
+ => GetEnumerator();
+
+ ///
+ public void Add(T item)
+ => throw new NotSupportedException("You may not change a SpanWrapper!");
+
+ ///
+ public void Clear()
+ => throw new NotSupportedException("You may not change a SpanWrapper!");
+
+ ///
+ public bool Contains(T item)
+ => _value.Contains(item);
+
+ ///
+ public void CopyTo(T[] array, int arrayIndex)
+ => _value.CopyTo(array, arrayIndex);
+
+ ///
+ public bool Remove(T item)
+ => throw new NotSupportedException("You may not change a SpanWrapper!");
+
+ ///
+ public int Count
+ => _value.Length;
+
+ ///
+ public bool IsReadOnly
+ => true;
+}
+#endif
diff --git a/Source/aweXpect.Core/Expect.cs b/Source/aweXpect.Core/Expect.cs
index 3e26cc57e..bd06fbf69 100644
--- a/Source/aweXpect.Core/Expect.cs
+++ b/Source/aweXpect.Core/Expect.cs
@@ -38,6 +38,26 @@ public static IThat That(Task subject,
=> new ThatSubject(new ExpectationBuilder(
new AsyncValueSource(subject), doNotPopulateThisValue));
+#if NET8_0_OR_GREATER
+ ///
+ /// Specify expectations for the current .
+ ///
+ public static IThat> That(ReadOnlySpan subject,
+ [CallerArgumentExpression("subject")] string doNotPopulateThisValue = "")
+ => new ThatSubject>(new ExpectationBuilder>(
+ new ValueSource>(new SpanWrapper(subject)), doNotPopulateThisValue));
+#endif
+
+#if NET8_0_OR_GREATER
+ ///
+ /// Specify expectations for the current .
+ ///
+ public static IThat> That(Span subject,
+ [CallerArgumentExpression("subject")] string doNotPopulateThisValue = "")
+ => new ThatSubject>(new ExpectationBuilder>(
+ new ValueSource>(new SpanWrapper(subject)), doNotPopulateThisValue));
+#endif
+
#if NET8_0_OR_GREATER
///
/// Specify expectations for the current asynchronous .
diff --git a/Source/aweXpect/Results/IsParsableResult.cs b/Source/aweXpect/Results/IsParsableResult.cs
index 822603d04..a9d35e48b 100644
--- a/Source/aweXpect/Results/IsParsableResult.cs
+++ b/Source/aweXpect/Results/IsParsableResult.cs
@@ -5,7 +5,8 @@
namespace aweXpect.Results;
///
-/// The result for verifying that the subject is parsable into .
+/// The result for verifying that the subject is parsable
+/// into .
///
public class IsParsableResult(
ExpectationBuilder expectationBuilder,
diff --git a/Source/aweXpect/Results/IsSpanParsableResult.cs b/Source/aweXpect/Results/IsSpanParsableResult.cs
new file mode 100644
index 000000000..405710942
--- /dev/null
+++ b/Source/aweXpect/Results/IsSpanParsableResult.cs
@@ -0,0 +1,35 @@
+#if NET8_0_OR_GREATER
+using System;
+using aweXpect.Core;
+
+namespace aweXpect.Results;
+
+///
+/// The result for verifying that the subject is parsable
+/// into .
+///
+public class IsSpanParsableResult(
+ ExpectationBuilder expectationBuilder,
+ IThat> subject,
+ IFormatProvider? formatProvider)
+ : AndOrResult, IThat>>(expectationBuilder, subject)
+ where TType : ISpanParsable
+{
+ private readonly ExpectationBuilder _expectationBuilder = expectationBuilder;
+
+ ///
+ /// Gives access to the parsed value.
+ ///
+ public IThat Which
+ => new ThatSubject(_expectationBuilder
+ .ForWhich, TType?>(d =>
+ {
+ if (TType.TryParse(d.AsSpan(), formatProvider, out TType? result))
+ {
+ return result;
+ }
+
+ return default;
+ }, " which "));
+}
+#endif
diff --git a/Source/aweXpect/Results/IsUtf8SpanParsableResult.cs b/Source/aweXpect/Results/IsUtf8SpanParsableResult.cs
new file mode 100644
index 000000000..c4d3ed08d
--- /dev/null
+++ b/Source/aweXpect/Results/IsUtf8SpanParsableResult.cs
@@ -0,0 +1,35 @@
+#if NET8_0_OR_GREATER
+using System;
+using aweXpect.Core;
+
+namespace aweXpect.Results;
+
+///
+/// The result for verifying that the subject is parsable
+/// into .
+///
+public class IsUtf8SpanParsableResult(
+ ExpectationBuilder expectationBuilder,
+ IThat> subject,
+ IFormatProvider? formatProvider)
+ : AndOrResult, IThat>>(expectationBuilder, subject)
+ where TType : IUtf8SpanParsable
+{
+ private readonly ExpectationBuilder _expectationBuilder = expectationBuilder;
+
+ ///
+ /// Gives access to the parsed value.
+ ///
+ public IThat Which
+ => new ThatSubject(_expectationBuilder
+ .ForWhich, TType?>(d =>
+ {
+ if (TType.TryParse(d.AsSpan(), formatProvider, out TType? result))
+ {
+ return result;
+ }
+
+ return default;
+ }, " which "));
+}
+#endif
diff --git a/Source/aweXpect/That/Spans/ThatSpan.IsParsableInto.cs b/Source/aweXpect/That/Spans/ThatSpan.IsParsableInto.cs
new file mode 100644
index 000000000..f8db273f5
--- /dev/null
+++ b/Source/aweXpect/That/Spans/ThatSpan.IsParsableInto.cs
@@ -0,0 +1,218 @@
+#if NET8_0_OR_GREATER
+using System;
+using aweXpect.Core;
+using aweXpect.Core.Constraints;
+using aweXpect.Helpers;
+using aweXpect.Results;
+
+namespace aweXpect;
+
+public static partial class ThatSpan
+{
+ ///
+ /// Verifies that the subject is parsable into type .
+ ///
+ ///
+ /// The optional parameter provides culture-specific formatting information
+ /// in the call to .
+ ///
+ public static IsSpanParsableResult IsParsableInto(
+ this IThat> source,
+ IFormatProvider? formatProvider = null)
+ where TType : ISpanParsable
+ => new(source.Get().ExpectationBuilder.AddConstraint((it, grammars)
+ => new IsParsableIntoConstraint(it, grammars, formatProvider)),
+ source,
+ formatProvider);
+
+ ///
+ /// Verifies that the subject is parsable into type .
+ ///
+ ///
+ /// The optional parameter provides culture-specific formatting information
+ /// in the call to .
+ ///
+ public static IsUtf8SpanParsableResult IsParsableInto(
+ this IThat> source,
+ IFormatProvider? formatProvider = null)
+ where TType : IUtf8SpanParsable
+ => new(source.Get().ExpectationBuilder.AddConstraint((it, grammars)
+ => new IsUtf8ParsableIntoConstraint(it, grammars, formatProvider)),
+ source,
+ formatProvider);
+
+ ///
+ /// Verifies that the subject is not parsable into type .
+ ///
+ ///
+ /// The optional parameter provides culture-specific formatting information
+ /// in the call to .
+ ///
+ public static AndOrResult, IThat>> IsNotParsableInto(
+ this IThat> source,
+ IFormatProvider? formatProvider = null)
+ where TType : ISpanParsable
+ => new(source.Get().ExpectationBuilder.AddConstraint((it, grammars)
+ => new IsParsableIntoConstraint(it, grammars, formatProvider).Invert()),
+ source);
+
+ ///
+ /// Verifies that the subject is not parsable into type .
+ ///
+ ///
+ /// The optional parameter provides culture-specific formatting information
+ /// in the call to .
+ ///
+ public static AndOrResult, IThat>> IsNotParsableInto(
+ this IThat> source,
+ IFormatProvider? formatProvider = null)
+ where TType : IUtf8SpanParsable
+ => new(source.Get().ExpectationBuilder.AddConstraint((it, grammars)
+ => new IsUtf8ParsableIntoConstraint(it, grammars, formatProvider).Invert()),
+ source);
+
+ private sealed class IsParsableIntoConstraint : ConstraintResult.WithValue>,
+ IValueConstraint>
+ where TType : ISpanParsable
+ {
+ private readonly IFormatProvider? _formatProvider;
+ private readonly string _it;
+ private string? _exceptionMessage;
+
+ public IsParsableIntoConstraint(string it,
+ ExpectationGrammars grammars,
+ IFormatProvider? formatProvider) : base(grammars)
+ {
+ _it = it;
+ _formatProvider = formatProvider;
+ FurtherProcessingStrategy = FurtherProcessingStrategy.IgnoreResult;
+ }
+
+ public ConstraintResult IsMetBy(SpanWrapper actual)
+ {
+ Actual = actual;
+
+ try
+ {
+ _ = TType.Parse(actual.AsSpan(), _formatProvider);
+ Outcome = Outcome.Success;
+ }
+ catch (Exception ex)
+ {
+ if (string.IsNullOrEmpty(ex.Message) || ex.Message.Length < 2)
+ {
+ _exceptionMessage = "an unknown error occurred";
+ }
+ else
+ {
+ _exceptionMessage = char.ToLowerInvariant(ex.Message[0]) + ex.Message[1..^1];
+ }
+
+ Outcome = Outcome.Failure;
+ }
+
+ return this;
+ }
+
+ protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
+ {
+ stringBuilder.Append("is parsable into ");
+ Formatter.Format(stringBuilder, typeof(TType));
+ if (_formatProvider is not null)
+ {
+ stringBuilder.Append(" using ");
+ Formatter.Format(stringBuilder, _formatProvider);
+ }
+ }
+
+ protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
+ => stringBuilder.Append(_it).Append(" was not because ").Append(_exceptionMessage);
+
+ protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
+ {
+ stringBuilder.Append("is not parsable into ");
+ Formatter.Format(stringBuilder, typeof(TType));
+ if (_formatProvider is not null)
+ {
+ stringBuilder.Append(" using ");
+ Formatter.Format(stringBuilder, _formatProvider);
+ }
+ }
+
+ protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
+ => stringBuilder.Append(_it).Append(" was");
+ }
+
+ private sealed class IsUtf8ParsableIntoConstraint : ConstraintResult.WithValue>,
+ IValueConstraint>
+ where TType : IUtf8SpanParsable
+ {
+ private readonly IFormatProvider? _formatProvider;
+ private readonly string _it;
+ private string? _exceptionMessage;
+
+ public IsUtf8ParsableIntoConstraint(string it,
+ ExpectationGrammars grammars,
+ IFormatProvider? formatProvider) : base(grammars)
+ {
+ _it = it;
+ _formatProvider = formatProvider;
+ FurtherProcessingStrategy = FurtherProcessingStrategy.IgnoreResult;
+ }
+
+ public ConstraintResult IsMetBy(SpanWrapper actual)
+ {
+ Actual = actual;
+
+ try
+ {
+ _ = TType.Parse(actual.AsSpan(), _formatProvider);
+ Outcome = Outcome.Success;
+ }
+ catch (Exception ex)
+ {
+ if (string.IsNullOrEmpty(ex.Message) || ex.Message.Length < 2)
+ {
+ _exceptionMessage = "an unknown error occurred";
+ }
+ else
+ {
+ _exceptionMessage = char.ToLowerInvariant(ex.Message[0]) + ex.Message[1..^1];
+ }
+
+ Outcome = Outcome.Failure;
+ }
+
+ return this;
+ }
+
+ protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
+ {
+ stringBuilder.Append("is parsable into ");
+ Formatter.Format(stringBuilder, typeof(TType));
+ if (_formatProvider is not null)
+ {
+ stringBuilder.Append(" using ");
+ Formatter.Format(stringBuilder, _formatProvider);
+ }
+ }
+
+ protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
+ => stringBuilder.Append(_it).Append(" was not because ").Append(_exceptionMessage);
+
+ protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
+ {
+ stringBuilder.Append("is not parsable into ");
+ Formatter.Format(stringBuilder, typeof(TType));
+ if (_formatProvider is not null)
+ {
+ stringBuilder.Append(" using ");
+ Formatter.Format(stringBuilder, _formatProvider);
+ }
+ }
+
+ protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
+ => stringBuilder.Append(_it).Append(" was");
+ }
+}
+#endif
diff --git a/Source/aweXpect/That/Spans/ThatSpan.cs b/Source/aweXpect/That/Spans/ThatSpan.cs
new file mode 100644
index 000000000..d3a135ce7
--- /dev/null
+++ b/Source/aweXpect/That/Spans/ThatSpan.cs
@@ -0,0 +1,10 @@
+#if NET8_0_OR_GREATER
+using aweXpect.Core;
+
+namespace aweXpect;
+
+///
+/// Expectations on values.
+///
+public static partial class ThatSpan;
+#endif
diff --git a/Source/aweXpect/aweXpect.csproj.DotSettings b/Source/aweXpect/aweXpect.csproj.DotSettings
index 13cb71e74..138aaf1ff 100644
--- a/Source/aweXpect/aweXpect.csproj.DotSettings
+++ b/Source/aweXpect/aweXpect.csproj.DotSettings
@@ -17,6 +17,7 @@
True
True
True
+ True
True
True
True
diff --git a/Tests/aweXpect.Api.Tests/Expected/aweXpect_net8.0.txt b/Tests/aweXpect.Api.Tests/Expected/aweXpect_net8.0.txt
index 96d6dfc55..acb945c1d 100644
--- a/Tests/aweXpect.Api.Tests/Expected/aweXpect_net8.0.txt
+++ b/Tests/aweXpect.Api.Tests/Expected/aweXpect_net8.0.txt
@@ -1088,6 +1088,17 @@ namespace aweXpect
public static aweXpect.Results.SignalCountWhoseResult Signaled(this aweXpect.Core.IThat> source) { }
public static aweXpect.Results.SignalCountWhoseResult Signaled(this aweXpect.Core.IThat> source, aweXpect.Core.Times times) { }
}
+ public static class ThatSpan
+ {
+ public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> IsNotParsableInto(this aweXpect.Core.IThat> source, System.IFormatProvider? formatProvider = null)
+ where TType : System.IUtf8SpanParsable { }
+ public static aweXpect.Results.AndOrResult, aweXpect.Core.IThat>> IsNotParsableInto(this aweXpect.Core.IThat> source, System.IFormatProvider? formatProvider = null)
+ where TType : System.ISpanParsable { }
+ public static aweXpect.Results.IsUtf8SpanParsableResult IsParsableInto(this aweXpect.Core.IThat> source, System.IFormatProvider? formatProvider = null)
+ where TType : System.IUtf8SpanParsable { }
+ public static aweXpect.Results.IsSpanParsableResult IsParsableInto(this aweXpect.Core.IThat> source, System.IFormatProvider? formatProvider = null)
+ where TType : System.ISpanParsable { }
+ }
public static class ThatStream
{
public static aweXpect.Results.PropertyResult.Long HasLength(this aweXpect.Core.IThat source) { }
@@ -1268,6 +1279,18 @@ namespace aweXpect.Results
public IsParsableResult(aweXpect.Core.ExpectationBuilder expectationBuilder, aweXpect.Core.IThat subject, System.IFormatProvider? formatProvider) { }
public aweXpect.Core.IThat Which { get; }
}
+ public class IsSpanParsableResult : aweXpect.Results.AndOrResult, aweXpect.Core.IThat>>
+ where TType : System.ISpanParsable
+ {
+ public IsSpanParsableResult(aweXpect.Core.ExpectationBuilder expectationBuilder, aweXpect.Core.IThat> subject, System.IFormatProvider? formatProvider) { }
+ public aweXpect.Core.IThat Which { get; }
+ }
+ public class IsUtf8SpanParsableResult : aweXpect.Results.AndOrResult, aweXpect.Core.IThat>>
+ where TType : System.IUtf8SpanParsable
+ {
+ public IsUtf8SpanParsableResult(aweXpect.Core.ExpectationBuilder expectationBuilder, aweXpect.Core.IThat> subject, System.IFormatProvider? formatProvider) { }
+ public aweXpect.Core.IThat Which { get; }
+ }
public class ObjectCollectionBeContainedInResult : aweXpect.Results.ObjectCollectionMatchResult
{
public ObjectCollectionBeContainedInResult(aweXpect.Core.ExpectationBuilder expectationBuilder, TThat returnValue, aweXpect.Options.ObjectEqualityOptions options, aweXpect.Options.CollectionMatchOptions collectionMatchOptions) { }
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 ed3cf455f..c86db747d 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
@@ -272,6 +272,20 @@ namespace aweXpect.Core
public aweXpect.Core.ResultContexts Remove(System.Predicate predicate) { }
public aweXpect.Core.ResultContexts Remove(string title, System.StringComparison stringComparison = 5) { }
}
+ public class SpanWrapper : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.IEnumerable
+ {
+ public SpanWrapper(System.ReadOnlySpan span) { }
+ public SpanWrapper(System.Span span) { }
+ public int Count { get; }
+ public bool IsReadOnly { get; }
+ public void Add(T item) { }
+ public System.Span AsSpan() { }
+ public void Clear() { }
+ public bool Contains(T item) { }
+ public void CopyTo(T[] array, int arrayIndex) { }
+ public System.Collections.Generic.IEnumerator GetEnumerator() { }
+ public bool Remove(T item) { }
+ }
public sealed class StringDifference
{
public StringDifference(string? actual, string? expected, System.Collections.Generic.IEqualityComparer? comparer = null, aweXpect.Core.StringDifferenceSettings? settings = null) { }
@@ -585,6 +599,8 @@ namespace aweXpect
public static aweXpect.Delegates.ThatDelegate.WithValue That(System.Func> @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string doNotPopulateThisValue = "") { }
public static aweXpect.Delegates.ThatDelegate.WithValue That(System.Func> @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string doNotPopulateThisValue = "") { }
public static aweXpect.Delegates.ThatDelegate.WithValue That(System.Func @delegate, [System.Runtime.CompilerServices.CallerArgumentExpression("delegate")] string doNotPopulateThisValue = "") { }
+ public static aweXpect.Core.IThat> That(System.ReadOnlySpan subject, [System.Runtime.CompilerServices.CallerArgumentExpression("subject")] string doNotPopulateThisValue = "") { }
+ public static aweXpect.Core.IThat> That(System.Span subject, [System.Runtime.CompilerServices.CallerArgumentExpression("subject")] string doNotPopulateThisValue = "") { }
public static aweXpect.Core.IThat That(System.Threading.Tasks.Task subject, [System.Runtime.CompilerServices.CallerArgumentExpression("subject")] string doNotPopulateThisValue = "") { }
public static aweXpect.Core.IThat That(System.Threading.Tasks.ValueTask subject, [System.Runtime.CompilerServices.CallerArgumentExpression("subject")] string doNotPopulateThisValue = "") { }
public static aweXpect.Core.IThat That(T subject, [System.Runtime.CompilerServices.CallerArgumentExpression("subject")] string doNotPopulateThisValue = "") { }
diff --git a/Tests/aweXpect.Core.Tests/Core/SpanWrapperTests.cs b/Tests/aweXpect.Core.Tests/Core/SpanWrapperTests.cs
new file mode 100644
index 000000000..610ecaacb
--- /dev/null
+++ b/Tests/aweXpect.Core.Tests/Core/SpanWrapperTests.cs
@@ -0,0 +1,89 @@
+#if NET8_0_OR_GREATER
+using aweXpect.Synchronous;
+
+// ReSharper disable CollectionNeverUpdated.Local
+// ReSharper disable CollectionNeverQueried.Local
+
+namespace aweXpect.Core.Tests.Core;
+
+public sealed class SpanWrapperTests
+{
+ [Fact]
+ public void Add_ShouldThrowNotSupportedException()
+ {
+ SpanWrapper sut = new("foo".AsSpan());
+
+ void Act()
+ => sut.Add('b');
+
+ Synchronously.Verify(That(Act).Throws()
+ .WithMessage("You may not change a SpanWrapper!"));
+ }
+
+ [Fact]
+ public void Clear_ShouldThrowNotSupportedException()
+ {
+ SpanWrapper sut = new("foo".AsSpan());
+
+ void Act()
+ => sut.Clear();
+
+ Synchronously.Verify(That(Act).Throws()
+ .WithMessage("You may not change a SpanWrapper!"));
+ }
+
+ [Theory]
+ [InlineData('o', true)]
+ [InlineData('x', false)]
+ public void Contains_ShouldReturnExpectedResult(char character, bool expectedResult)
+ {
+ SpanWrapper sut = new("foo".AsSpan());
+
+ bool result = sut.Contains(character);
+
+ Synchronously.Verify(That(result).IsEqualTo(expectedResult));
+ }
+
+ [Fact]
+ public void CopyTo_ShouldCopyValuesToArray()
+ {
+ char[] buffer = "some-prefilled-buffer".ToCharArray();
+ SpanWrapper sut = new("foo".AsSpan());
+
+ sut.CopyTo(buffer, 1);
+
+ Synchronously.Verify(That(new string(buffer)).IsEqualTo("sfoo-prefilled-buffer"));
+ }
+
+ [Theory]
+ [InlineData("foo", 3)]
+ [InlineData("foobar", 6)]
+ public void Count_ShouldReturnExpectedLength(string subject, int expectedLength)
+ {
+ SpanWrapper sut = new(subject.AsSpan());
+
+ Synchronously.Verify(That(sut.Count).IsEqualTo(expectedLength));
+ }
+
+ [Fact]
+ public void IsReadOnly_ShouldBeTrue()
+ {
+ Span span = [1, 2, 3,];
+ SpanWrapper sut = new(span);
+
+ Synchronously.Verify(That(sut.IsReadOnly).IsTrue());
+ }
+
+ [Fact]
+ public void Remove_ShouldThrowNotSupportedException()
+ {
+ SpanWrapper sut = new("foo".AsSpan());
+
+ void Act()
+ => sut.Remove('b');
+
+ Synchronously.Verify(That(Act).Throws()
+ .WithMessage("You may not change a SpanWrapper!"));
+ }
+}
+#endif
diff --git a/Tests/aweXpect.Core.Tests/ExpectTests.cs b/Tests/aweXpect.Core.Tests/ExpectTests.cs
index 4027d58ba..2b97fcf11 100644
--- a/Tests/aweXpect.Core.Tests/ExpectTests.cs
+++ b/Tests/aweXpect.Core.Tests/ExpectTests.cs
@@ -115,6 +115,7 @@ private sealed class MyExpectation(Expectation.Result result, params ResultConte
{
internal override Task GetResult(int index, Dictionary outcomes)
=> Task.FromResult(result);
+
internal override IEnumerable GetContexts(int index, Dictionary outcomes)
=> contexts;
}
diff --git a/Tests/aweXpect.Tests/Spans/ThatSpan.IsNotParsableInto.Tests.cs b/Tests/aweXpect.Tests/Spans/ThatSpan.IsNotParsableInto.Tests.cs
new file mode 100644
index 000000000..c79df8349
--- /dev/null
+++ b/Tests/aweXpect.Tests/Spans/ThatSpan.IsNotParsableInto.Tests.cs
@@ -0,0 +1,55 @@
+#if NET8_0_OR_GREATER
+using System.Globalization;
+
+namespace aweXpect.Tests;
+
+public sealed partial class ThatSpan
+{
+ public sealed partial class IsNotParsableInto
+ {
+ public sealed class Tests
+ {
+ [Fact]
+ public async Task WhenSpanIsNotParsable_ShouldSucceed()
+ {
+ async Task Act()
+ => await That("abc".AsSpan()).IsNotParsableInto();
+
+ await That(Act).DoesNotThrow();
+ }
+
+ [Fact]
+ public async Task WhenSpanIsParsable_ShouldFail()
+ {
+ async Task Act()
+ => await That("42".AsSpan()).IsNotParsableInto();
+
+ await That(Act).Throws()
+ .WithMessage("""
+ Expected that "42".AsSpan()
+ is not parsable into int,
+ but it was
+ """);
+ }
+
+ [Theory]
+ [InlineData("12,34", "de-AT")]
+ [InlineData("12.34", "en-US")]
+ public async Task WithFormatProvider_ShouldBeUsed(string subject, string cultureName)
+ {
+ IFormatProvider formatProvider = new CultureInfo(cultureName);
+
+ async Task Act()
+ => await That(subject.AsSpan()).IsNotParsableInto(formatProvider);
+
+ await That(Act).Throws()
+ .WithMessage($"""
+ Expected that subject.AsSpan()
+ is not parsable into decimal using {cultureName},
+ but it was
+ """);
+ }
+ }
+ }
+}
+#endif
diff --git a/Tests/aweXpect.Tests/Spans/ThatSpan.IsNotParsableInto.Utf8Tests.cs b/Tests/aweXpect.Tests/Spans/ThatSpan.IsNotParsableInto.Utf8Tests.cs
new file mode 100644
index 000000000..b9d780bd3
--- /dev/null
+++ b/Tests/aweXpect.Tests/Spans/ThatSpan.IsNotParsableInto.Utf8Tests.cs
@@ -0,0 +1,61 @@
+#if NET8_0_OR_GREATER
+using System.Globalization;
+using System.Text;
+
+namespace aweXpect.Tests;
+
+public sealed partial class ThatSpan
+{
+ public sealed partial class IsNotParsableInto
+ {
+ public sealed class Utf8Tests
+ {
+ [Fact]
+ public async Task WhenSpanIsNotParsable_ShouldSucceed()
+ {
+ byte[] subject = "abc"u8.ToArray();
+
+ async Task Act()
+ => await That(subject.AsSpan()).IsNotParsableInto();
+
+ await That(Act).DoesNotThrow();
+ }
+
+ [Fact]
+ public async Task WhenSpanIsParsable_ShouldFail()
+ {
+ byte[] subject = "42"u8.ToArray();
+
+ async Task Act()
+ => await That(subject.AsSpan()).IsNotParsableInto();
+
+ await That(Act).Throws()
+ .WithMessage("""
+ Expected that subject.AsSpan()
+ is not parsable into int,
+ but it was
+ """);
+ }
+
+ [Theory]
+ [InlineData("12,34", "de-AT")]
+ [InlineData("12.34", "en-US")]
+ public async Task WithFormatProvider_ShouldBeUsed(string subjectString, string cultureName)
+ {
+ byte[] subject = Encoding.UTF8.GetBytes(subjectString);
+ IFormatProvider formatProvider = new CultureInfo(cultureName);
+
+ async Task Act()
+ => await That(subject.AsSpan()).IsNotParsableInto(formatProvider);
+
+ await That(Act).Throws()
+ .WithMessage($"""
+ Expected that subject.AsSpan()
+ is not parsable into decimal using {cultureName},
+ but it was
+ """);
+ }
+ }
+ }
+}
+#endif
diff --git a/Tests/aweXpect.Tests/Spans/ThatSpan.IsParsableInto.Tests.cs b/Tests/aweXpect.Tests/Spans/ThatSpan.IsParsableInto.Tests.cs
new file mode 100644
index 000000000..5b86da4a3
--- /dev/null
+++ b/Tests/aweXpect.Tests/Spans/ThatSpan.IsParsableInto.Tests.cs
@@ -0,0 +1,107 @@
+#if NET8_0_OR_GREATER
+using System.Globalization;
+
+namespace aweXpect.Tests;
+
+public sealed partial class ThatSpan
+{
+ public sealed partial class IsParsableInto
+ {
+ public sealed class Tests
+ {
+ [Fact]
+ public async Task WhenSpanIsNotParsable_ShouldFail()
+ {
+ async Task Act()
+ => await That("abc".AsSpan()).IsParsableInto();
+
+ await That(Act).Throws()
+ .WithMessage("""
+ Expected that "abc".AsSpan()
+ is parsable into int,
+ but it was not because the input string 'abc' was not in a correct format
+ """);
+ }
+
+ [Fact]
+ public async Task WhenSpanIsParsable_ShouldSucceed()
+ {
+ async Task Act()
+ => await That("42".AsSpan()).IsParsableInto();
+
+ await That(Act).DoesNotThrow();
+ }
+
+ [Theory]
+ [InlineData("-12.34", "de-AT")]
+ [InlineData("-12,34", "en-US")]
+ public async Task WithFormatProvider_WhenFormatDoesNotMatch_ShouldFail(string subject, string cultureName)
+ {
+ IFormatProvider formatProvider = new CultureInfo(cultureName);
+
+ async Task Act()
+ => await That(subject.AsSpan()).IsParsableInto(formatProvider);
+
+ await That(Act).Throws()
+ .WithMessage($"""
+ Expected that subject.AsSpan()
+ is parsable into uint using {cultureName},
+ but it was not because the input string '{subject}' was not in a correct format
+ """);
+ }
+
+ [Theory]
+ [InlineData("12,34", "de-AT")]
+ [InlineData("12.34", "en-US")]
+ public async Task WithFormatProvider_WhenFormatMatches_ShouldSucceed(string subject, string cultureName)
+ {
+ IFormatProvider formatProvider = new CultureInfo(cultureName);
+
+ async Task Act()
+ => await That(subject.AsSpan()).IsParsableInto(formatProvider);
+
+ await That(Act).DoesNotThrow();
+ }
+ }
+
+ public sealed class WhichTests
+ {
+ [Fact]
+ public async Task WhenSpanIsNotParsable_ShouldFail()
+ {
+ async Task Act()
+ => await That("abc".AsSpan()).IsParsableInto().Which.IsLessThan(10.Seconds());
+
+ await That(Act).Throws()
+ .WithMessage("""
+ Expected that "abc".AsSpan()
+ is parsable into TimeSpan which is less than 0:10,
+ but it was not because string 'abc' was not recognized as a valid TimeSpan
+ """);
+ }
+
+ [Fact]
+ public async Task WhenSpanIsParsable_ShouldSucceed()
+ {
+ async Task Act()
+ => await That("42".AsSpan()).IsParsableInto().Which.IsBetween(41).And(43);
+
+ await That(Act).DoesNotThrow();
+ }
+
+ [Theory]
+ [InlineData("12,34", "de-AT")]
+ [InlineData("12.34", "en-US")]
+ public async Task WithFormatProvider_ShouldBeUsed(string subject, string cultureName)
+ {
+ IFormatProvider formatProvider = new CultureInfo(cultureName);
+
+ async Task Act()
+ => await That(subject.AsSpan()).IsParsableInto(formatProvider).Which.IsEqualTo(12.34M);
+
+ await That(Act).DoesNotThrow();
+ }
+ }
+ }
+}
+#endif
diff --git a/Tests/aweXpect.Tests/Spans/ThatSpan.IsParsableInto.Utf8Tests.cs b/Tests/aweXpect.Tests/Spans/ThatSpan.IsParsableInto.Utf8Tests.cs
new file mode 100644
index 000000000..a2ff3c8d4
--- /dev/null
+++ b/Tests/aweXpect.Tests/Spans/ThatSpan.IsParsableInto.Utf8Tests.cs
@@ -0,0 +1,118 @@
+#if NET8_0_OR_GREATER
+using System.Globalization;
+using System.Text;
+
+namespace aweXpect.Tests;
+
+public sealed partial class ThatSpan
+{
+ public sealed partial class IsParsableInto
+ {
+ public sealed class Utf8Tests
+ {
+ [Fact]
+ public async Task WhenSpanIsNotParsable_ShouldFail()
+ {
+ byte[] subject = "abc"u8.ToArray();
+
+ async Task Act()
+ => await That(subject.AsSpan()).IsParsableInto();
+
+ await That(Act).Throws()
+ .WithMessage("""
+ Expected that subject.AsSpan()
+ is parsable into int,
+ but it was not because the input string 'System.ReadOnlySpan[3]' was not in a correct format
+ """);
+ }
+
+ [Fact]
+ public async Task WhenSpanIsParsable_ShouldSucceed()
+ {
+ byte[] subject = "42"u8.ToArray();
+
+ async Task Act()
+ => await That(subject.AsSpan()).IsParsableInto();
+
+ await That(Act).DoesNotThrow();
+ }
+
+ [Theory]
+ [InlineData("-12.34", "de-AT")]
+ [InlineData("-12,34", "en-US")]
+ public async Task WithFormatProvider_WhenFormatDoesNotMatch_ShouldFail(string subjectString,
+ string cultureName)
+ {
+ byte[] subject = Encoding.UTF8.GetBytes(subjectString);
+ IFormatProvider formatProvider = new CultureInfo(cultureName);
+
+ async Task Act()
+ => await That(subject.AsSpan()).IsParsableInto(formatProvider);
+
+ await That(Act).Throws()
+ .WithMessage($"""
+ Expected that subject.AsSpan()
+ is parsable into uint using {cultureName},
+ but it was not because the input string 'System.ReadOnlySpan[6]' was not in a correct format
+ """);
+ }
+
+ [Theory]
+ [InlineData("12,34", "de-AT")]
+ [InlineData("12.34", "en-US")]
+ public async Task WithFormatProvider_WhenFormatMatches_ShouldSucceed(string subjectString,
+ string cultureName)
+ {
+ byte[] subject = Encoding.UTF8.GetBytes(subjectString);
+ IFormatProvider formatProvider = new CultureInfo(cultureName);
+
+ async Task Act()
+ => await That(subject.AsSpan()).IsParsableInto(formatProvider);
+
+ await That(Act).DoesNotThrow();
+ }
+ }
+
+ public sealed class Utf8WhichTests
+ {
+ [Fact]
+ public async Task WhenSpanIsNotParsable_ShouldFail()
+ {
+ byte[] subject = "abc"u8.ToArray();
+
+ async Task Act()
+ => await That(subject.AsSpan()).IsParsableInto().Which.IsLessThan(10.0);
+
+ await That(Act).Throws()
+ .WithMessage("""
+ Expected that subject.AsSpan()
+ is parsable into double which is less than 10.0,
+ but it was not because the input string 'System.ReadOnlySpan[3]' was not in a correct format
+ """);
+ }
+
+ [Fact]
+ public async Task WhenSpanIsParsable_ShouldSucceed()
+ {
+ async Task Act()
+ => await That("42".AsSpan()).IsParsableInto().Which.IsBetween(41).And(43);
+
+ await That(Act).DoesNotThrow();
+ }
+
+ [Theory]
+ [InlineData("12,34", "de-AT")]
+ [InlineData("12.34", "en-US")]
+ public async Task WithFormatProvider_ShouldBeUsed(string subject, string cultureName)
+ {
+ IFormatProvider formatProvider = new CultureInfo(cultureName);
+
+ async Task Act()
+ => await That(subject.AsSpan()).IsParsableInto(formatProvider).Which.IsEqualTo(12.34M);
+
+ await That(Act).DoesNotThrow();
+ }
+ }
+ }
+}
+#endif
diff --git a/Tests/aweXpect.Tests/Spans/ThatSpan.Tests.cs b/Tests/aweXpect.Tests/Spans/ThatSpan.Tests.cs
new file mode 100644
index 000000000..badba5add
--- /dev/null
+++ b/Tests/aweXpect.Tests/Spans/ThatSpan.Tests.cs
@@ -0,0 +1,43 @@
+#if NET8_0_OR_GREATER
+namespace aweXpect.Tests;
+
+public sealed partial class ThatSpan
+{
+ public sealed class Tests
+ {
+ [Fact]
+ public async Task ShouldSupportIsEmpty()
+ {
+ var subject = new[]
+ {
+ 1, 2, 3,
+ };
+ async Task Act()
+ => await That(subject.AsSpan()).IsEmpty();
+
+ await That(Act).Throws()
+ .WithMessage("""
+ Expected that subject.AsSpan()
+ is empty,
+ but it was [
+ 1,
+ 2,
+ 3
+ ]
+ """);
+ }
+
+ [Fact]
+ public async Task ShouldSupportIsInAscendingOrder()
+ {
+ async Task Act()
+ => await That(new[]
+ {
+ 1, 2, 3,
+ }.AsSpan()).IsInAscendingOrder();
+
+ await That(Act).DoesNotThrow();
+ }
+ }
+}
+#endif
diff --git a/Tests/aweXpect.Tests/Spans/ThatSpan.cs b/Tests/aweXpect.Tests/Spans/ThatSpan.cs
new file mode 100644
index 000000000..57c0aae99
--- /dev/null
+++ b/Tests/aweXpect.Tests/Spans/ThatSpan.cs
@@ -0,0 +1,7 @@
+#if NET8_0_OR_GREATER
+namespace aweXpect.Tests;
+
+public sealed partial class ThatSpan
+{
+}
+#endif
diff --git a/Tests/aweXpect.Tests/aweXpect.Tests.csproj.DotSettings b/Tests/aweXpect.Tests/aweXpect.Tests.csproj.DotSettings
index 6de2cb12f..73ecc708b 100644
--- a/Tests/aweXpect.Tests/aweXpect.Tests.csproj.DotSettings
+++ b/Tests/aweXpect.Tests/aweXpect.Tests.csproj.DotSettings
@@ -15,6 +15,7 @@
True
True
True
+ True
True
True
True