From 360fb8d0b587e098a6b7c49fb6c03b147909e000 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Mon, 21 Jul 2025 21:32:52 +0200 Subject: [PATCH 1/2] feat: avoid the need to specify `AtAnyIndex` Instead allow simply specifying `HasItem()` or `HasItemThat()` --- .../pages/docs/expectations/07-collections.md | 4 +-- Pipeline/Build.cs | 2 +- Source/aweXpect.Core/Results/HasItemResult.cs | 12 ++----- .../Expected/aweXpect.Core_net8.0.txt | 3 +- .../Expected/aweXpect.Core_netstandard2.0.txt | 3 +- .../ThatAsyncEnumerable.HasItem.Tests.cs | 34 +++++++++---------- .../ThatAsyncEnumerable.HasItemThat.Tests.cs | 6 ++-- .../ThatEnumerable.HasItem.EnumerableTests.cs | 28 +++++++-------- .../ThatEnumerable.HasItem.ImmutableTests.cs | 16 ++++----- .../ThatEnumerable.HasItem.Tests.cs | 34 +++++++++---------- ...atEnumerable.HasItemThat.ImmutableTests.cs | 2 +- .../ThatEnumerable.HasItemThat.Tests.cs | 8 ++--- 12 files changed, 71 insertions(+), 81 deletions(-) diff --git a/Docs/pages/docs/expectations/07-collections.md b/Docs/pages/docs/expectations/07-collections.md index ad6b4220b..146235318 100644 --- a/Docs/pages/docs/expectations/07-collections.md +++ b/Docs/pages/docs/expectations/07-collections.md @@ -496,7 +496,7 @@ You can verify that the collection contains an item that satisfies the expectati IEnumerable values = ["0th item", "1st item", "2nd item", "3rd item"]; await Expect.That(values).HasItem("1st item").AtIndex(1); -await Expect.That(values).HasItem(it => it.StartsWith("2nd")).AtAnyIndex(); +await Expect.That(values).HasItem(it => it.StartsWith("2nd")); // at any index ``` You can also use expectations on the individual items. @@ -505,7 +505,7 @@ You can also use expectations on the individual items. IEnumerable values = ["0th item", "1st item", "2nd item", "3rd item"]; await Expect.That(values).HasItemThat(it => it.IsEqualTo("1st item")).AtIndex(1); -await Expect.That(values).HasItemThat(it => it.StartsWith("2nd").And.EndsWith("item")).AtAnyIndex(); +await Expect.That(values).HasItemThat(it => it.StartsWith("2nd").And.EndsWith("item")); // at any index ``` *Note: The same expectation works also for `IAsyncEnumerable`.* 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/Results/HasItemResult.cs b/Source/aweXpect.Core/Results/HasItemResult.cs index 23be805b8..b022a32e3 100644 --- a/Source/aweXpect.Core/Results/HasItemResult.cs +++ b/Source/aweXpect.Core/Results/HasItemResult.cs @@ -13,22 +13,14 @@ public class HasItemResult( ExpectationBuilder expectationBuilder, IThat collection, CollectionIndexOptions collectionIndexOptions) + : AndOrResult>(expectationBuilder, collection) { - /// - /// …at any index. - /// - public AndOrResult> AtAnyIndex() - { - collectionIndexOptions.SetIndexRange(null, null); - return new AndOrResult>(expectationBuilder, collection); - } - /// /// …at the given . /// public AndOrResult> AtIndex(int index) { collectionIndexOptions.SetIndexRange(index, index); - return new AndOrResult>(expectationBuilder, collection); + return this; } } 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 80520748e..2cd09ee78 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 @@ -1087,10 +1087,9 @@ namespace aweXpect.Results public TSelf WithCancellation(System.Threading.CancellationToken cancellationToken) { } public TSelf WithTimeout(System.TimeSpan timeout) { } } - public class HasItemResult + public class HasItemResult : aweXpect.Results.AndOrResult> { public HasItemResult(aweXpect.Core.ExpectationBuilder expectationBuilder, aweXpect.Core.IThat collection, aweXpect.Options.CollectionIndexOptions collectionIndexOptions) { } - public aweXpect.Results.AndOrResult> AtAnyIndex() { } public aweXpect.Results.AndOrResult> AtIndex(int index) { } } public class NullableNumberToleranceResult : aweXpect.Results.NullableNumberToleranceResult> 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 a00956315..a9b716c33 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 @@ -1070,10 +1070,9 @@ namespace aweXpect.Results public TSelf WithCancellation(System.Threading.CancellationToken cancellationToken) { } public TSelf WithTimeout(System.TimeSpan timeout) { } } - public class HasItemResult + public class HasItemResult : aweXpect.Results.AndOrResult> { public HasItemResult(aweXpect.Core.ExpectationBuilder expectationBuilder, aweXpect.Core.IThat collection, aweXpect.Options.CollectionIndexOptions collectionIndexOptions) { } - public aweXpect.Results.AndOrResult> AtAnyIndex() { } public aweXpect.Results.AndOrResult> AtIndex(int index) { } } public class NullableNumberToleranceResult : aweXpect.Results.NullableNumberToleranceResult> diff --git a/Tests/aweXpect.Tests/Collections/ThatAsyncEnumerable.HasItem.Tests.cs b/Tests/aweXpect.Tests/Collections/ThatAsyncEnumerable.HasItem.Tests.cs index 45fa7cb23..6673c38a0 100644 --- a/Tests/aweXpect.Tests/Collections/ThatAsyncEnumerable.HasItem.Tests.cs +++ b/Tests/aweXpect.Tests/Collections/ThatAsyncEnumerable.HasItem.Tests.cs @@ -19,7 +19,7 @@ public async Task DoesNotEnumerateTwice() ThrowWhenIteratingTwiceAsyncEnumerable subject = new(); async Task Act() - => await That(subject).HasItem(_ => true).AtAnyIndex() + => await That(subject).HasItem(_ => true) .And.HasItem(_ => true).AtIndex(0); await That(Act).DoesNotThrow(); @@ -31,7 +31,7 @@ public async Task DoesNotMaterializeEnumerable() IAsyncEnumerable subject = Factory.GetAsyncFibonacciNumbers(); async Task Act() - => await That(subject).HasItem(a => a == 5).AtAnyIndex(); + => await That(subject).HasItem(a => a == 5); await That(Act).DoesNotThrow(); } @@ -91,7 +91,7 @@ public async Task WhenEnumerableIsEmpty_ShouldFail() IAsyncEnumerable subject = ToAsyncEnumerable(Array.Empty()); async Task Act() - => await That(subject).HasItem(_ => true).AtAnyIndex(); + => await That(subject).HasItem(_ => true); await That(Act).Throws() .WithMessage(""" @@ -110,7 +110,7 @@ public async Task WhenSubjectIsNull_WithAnyIndex_ShouldFail() IAsyncEnumerable? subject = null; async Task Act() - => await That(subject).HasItem(_ => true).AtAnyIndex(); + => await That(subject).HasItem(_ => true); await That(Act).Throws() .WithMessage(""" @@ -144,7 +144,7 @@ public async Task WithMultipleFailures_ShouldIncludeCollectionOnlyOnce() async Task Act() => await That(subject).HasItem(_ => false).AtIndex(0).And.HasItem(_ => false).AtIndex(1).And .HasItem(_ => false) - .AtAnyIndex(); + ; await That(Act).Throws() .WithMessage(""" @@ -170,7 +170,7 @@ public async Task DoesNotEnumerateTwice() ThrowWhenIteratingTwiceAsyncEnumerable subject = new(); async Task Act() - => await That(subject).HasItem(1).AtAnyIndex() + => await That(subject).HasItem(1) .And.HasItem(1).AtIndex(0); await That(Act).DoesNotThrow(); @@ -182,7 +182,7 @@ public async Task DoesNotMaterializeEnumerable() IAsyncEnumerable subject = Factory.GetAsyncFibonacciNumbers(); async Task Act() - => await That(subject).HasItem(5).AtAnyIndex(); + => await That(subject).HasItem(5); await That(Act).DoesNotThrow(); } @@ -243,7 +243,7 @@ public async Task WhenEnumerableIsEmpty_ShouldFail(int expected) IAsyncEnumerable subject = ToAsyncEnumerable(Array.Empty()); async Task Act() - => await That(subject).HasItem(expected).AtAnyIndex(); + => await That(subject).HasItem(expected); await That(Act).Throws() .WithMessage($""" @@ -263,7 +263,7 @@ public async Task WhenSubjectIsNull_WithAnyIndex_ShouldFail() IAsyncEnumerable? subject = null; async Task Act() - => await That(subject).HasItem(expected).AtAnyIndex(); + => await That(subject).HasItem(expected); await That(Act).Throws() .WithMessage(""" @@ -297,7 +297,7 @@ public async Task WithMultipleFailures_ShouldIncludeCollectionOnlyOnce() async Task Act() => await That(subject).HasItem(4).AtIndex(0).And.HasItem(5).AtIndex(1).And.HasItem(6) - .AtAnyIndex(); + ; await That(Act).Throws() .WithMessage(""" @@ -614,7 +614,7 @@ public async Task WhenEnumerableIsEmpty_ShouldFail() IAsyncEnumerable subject = ToAsyncEnumerable(Array.Empty()); async Task Act() - => await That(subject).HasItem("foo").AtAnyIndex(); + => await That(subject).HasItem("foo"); await That(Act).Throws() .WithMessage(""" @@ -633,7 +633,7 @@ public async Task WhenSubjectIsNull_WithAnyIndex_ShouldFail() IAsyncEnumerable? subject = null; async Task Act() - => await That(subject!).HasItem("foo").AtAnyIndex(); + => await That(subject!).HasItem("foo"); await That(Act).Throws() .WithMessage(""" @@ -700,7 +700,7 @@ public async Task WithMultipleFailures_ShouldIncludeCollectionOnlyOnce() async Task Act() => await That(subject).HasItem("d").AtIndex(0).And.HasItem("e").AtIndex(1).And.HasItem("f") - .AtAnyIndex(); + ; await That(Act).Throws() .WithMessage(""" @@ -728,7 +728,7 @@ public async Task WhenEquivalentItemIsFound_ShouldSucceed() MyClass expected = new(5); async Task Act() - => await That(subject).HasItem(expected).Equivalent().AtAnyIndex(); + => await That(subject).HasItem(expected).Equivalent(); await That(Act).DoesNotThrow(); } @@ -741,7 +741,7 @@ public async Task WhenEquivalentItemIsNotFound_ShouldFail() MyClass expected = new(4); async Task Act() - => await That(subject).HasItem(expected).Equivalent().AtAnyIndex(); + => await That(subject).HasItem(expected).Equivalent(); await That(Act).Throws() .WithMessage(""" @@ -808,7 +808,7 @@ public async Task WithAllDifferentComparer_ShouldFail() IAsyncEnumerable subject = Factory.GetAsyncFibonacciNumbers(20); async Task Act() - => await That(subject).HasItem(1).Using(new AllDifferentComparer()).AtAnyIndex(); + => await That(subject).HasItem(1).Using(new AllDifferentComparer()); await That(Act).Throws() .WithMessage(""" @@ -839,7 +839,7 @@ public async Task WithAllEqualComparer_ShouldSucceed() IAsyncEnumerable subject = Factory.GetAsyncFibonacciNumbers(20); async Task Act() - => await That(subject).HasItem(4).Using(new AllEqualComparer()).AtAnyIndex(); + => await That(subject).HasItem(4).Using(new AllEqualComparer()); await That(Act).DoesNotThrow(); } diff --git a/Tests/aweXpect.Tests/Collections/ThatAsyncEnumerable.HasItemThat.Tests.cs b/Tests/aweXpect.Tests/Collections/ThatAsyncEnumerable.HasItemThat.Tests.cs index 3e97fa695..4cc0c17ce 100644 --- a/Tests/aweXpect.Tests/Collections/ThatAsyncEnumerable.HasItemThat.Tests.cs +++ b/Tests/aweXpect.Tests/Collections/ThatAsyncEnumerable.HasItemThat.Tests.cs @@ -17,7 +17,7 @@ public async Task DoesNotMaterializeEnumerable() IAsyncEnumerable subject = Factory.GetAsyncFibonacciNumbers(); async Task Act() - => await That(subject).HasItemThat(it => it.IsEqualTo(5)).AtAnyIndex(); + => await That(subject).HasItemThat(it => it.IsEqualTo(5)); await That(Act).DoesNotThrow(); } @@ -77,7 +77,7 @@ public async Task WhenEnumerableIsEmpty_ShouldFail() IAsyncEnumerable subject = ToAsyncEnumerable(Array.Empty()); async Task Act() - => await That(subject).HasItemThat(it => it.IsNotEqualTo(0)).AtAnyIndex(); + => await That(subject).HasItemThat(it => it.IsNotEqualTo(0)); await That(Act).Throws() .WithMessage(""" @@ -96,7 +96,7 @@ public async Task WhenSubjectIsNull_WithAnyIndex_ShouldFail() IAsyncEnumerable? subject = null; async Task Act() - => await That(subject).HasItemThat(it => it.IsNotEqualTo(0)).AtAnyIndex(); + => await That(subject).HasItemThat(it => it.IsNotEqualTo(0)); await That(Act).Throws() .WithMessage(""" diff --git a/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItem.EnumerableTests.cs b/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItem.EnumerableTests.cs index 0a86be961..80c8d8520 100644 --- a/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItem.EnumerableTests.cs +++ b/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItem.EnumerableTests.cs @@ -19,7 +19,7 @@ public async Task DoesNotEnumerateTwice() IEnumerable subject = new ThrowWhenIteratingTwiceEnumerable(); async Task Act() - => await That(subject).HasItem(_ => true).AtAnyIndex() + => await That(subject).HasItem(_ => true) .And.HasItem(_ => true).AtIndex(0); await That(Act).DoesNotThrow(); @@ -31,7 +31,7 @@ public async Task DoesNotMaterializeEnumerable() IEnumerable subject = Factory.GetFibonacciNumbers(); async Task Act() - => await That(subject).HasItem(a => 5.Equals(a)).AtAnyIndex(); + => await That(subject).HasItem(a => 5.Equals(a)); await That(Act).DoesNotThrow(); } @@ -91,7 +91,7 @@ public async Task WhenEnumerableIsEmpty_ShouldFail() IEnumerable subject = Array.Empty(); async Task Act() - => await That(subject).HasItem(_ => true).AtAnyIndex(); + => await That(subject).HasItem(_ => true); await That(Act).Throws() .WithMessage(""" @@ -110,7 +110,7 @@ public async Task WhenSubjectIsNull_WithAnyIndex_ShouldFail() IEnumerable? subject = null; async Task Act() - => await That(subject!).HasItem(_ => true).AtAnyIndex(); + => await That(subject!).HasItem(_ => true); await That(Act).Throws() .WithMessage(""" @@ -144,7 +144,7 @@ public async Task WithMultipleFailures_ShouldIncludeCollectionOnlyOnce() async Task Act() => await That(subject).HasItem(_ => false).AtIndex(0).And.HasItem(_ => false).AtIndex(1).And .HasItem(_ => false) - .AtAnyIndex(); + ; await That(Act).Throws() .WithMessage(""" @@ -170,7 +170,7 @@ public async Task DoesNotEnumerateTwice() IEnumerable subject = new ThrowWhenIteratingTwiceEnumerable(); async Task Act() - => await That(subject).HasItem(1).AtAnyIndex() + => await That(subject).HasItem(1) .And.HasItem(1).AtIndex(0); await That(Act).DoesNotThrow(); @@ -182,7 +182,7 @@ public async Task DoesNotMaterializeEnumerable() IEnumerable subject = Factory.GetFibonacciNumbers(); async Task Act() - => await That(subject).HasItem(5).AtAnyIndex(); + => await That(subject).HasItem(5); await That(Act).DoesNotThrow(); } @@ -254,7 +254,7 @@ public async Task WhenEnumerableIsEmpty_ShouldFail(int expected) IEnumerable subject = Array.Empty(); async Task Act() - => await That(subject).HasItem(expected).AtAnyIndex(); + => await That(subject).HasItem(expected); await That(Act).Throws() .WithMessage($""" @@ -274,7 +274,7 @@ public async Task WhenSubjectIsNull_WithAnyIndex_ShouldFail() IEnumerable? subject = null; async Task Act() - => await That(subject!).HasItem(expected).AtAnyIndex(); + => await That(subject!).HasItem(expected); await That(Act).Throws() .WithMessage(""" @@ -308,7 +308,7 @@ public async Task WithMultipleFailures_ShouldIncludeCollectionOnlyOnce() async Task Act() => await That(subject).HasItem("d").AtIndex(0).And.HasItem("e").AtIndex(1).And.HasItem("f") - .AtAnyIndex(); + ; await That(Act).Throws() .WithMessage(""" @@ -335,7 +335,7 @@ public async Task WhenEquivalentItemIsFound_ShouldSucceed() MyClass expected = new(5); async Task Act() - => await That(subject).HasItem(expected).Equivalent().AtAnyIndex(); + => await That(subject).HasItem(expected).Equivalent(); await That(Act).DoesNotThrow(); } @@ -347,7 +347,7 @@ public async Task WhenEquivalentItemIsNotFound_ShouldFail() MyClass expected = new(4); async Task Act() - => await That(subject).HasItem(expected).Equivalent().AtAnyIndex(); + => await That(subject).HasItem(expected).Equivalent(); await That(Act).Throws() .WithMessage(""" @@ -414,7 +414,7 @@ public async Task WithAllDifferentComparer_ShouldFail() IEnumerable subject = Factory.GetFibonacciNumbers(20); async Task Act() - => await That(subject).HasItem(1).Using(new AllDifferentComparer()).AtAnyIndex(); + => await That(subject).HasItem(1).Using(new AllDifferentComparer()); await That(Act).Throws() .WithMessage(""" @@ -445,7 +445,7 @@ public async Task WithAllEqualComparer_ShouldSucceed() IEnumerable subject = Factory.GetFibonacciNumbers(20); async Task Act() - => await That(subject).HasItem(4).Using(new AllEqualComparer()).AtAnyIndex(); + => await That(subject).HasItem(4).Using(new AllEqualComparer()); await That(Act).DoesNotThrow(); } diff --git a/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItem.ImmutableTests.cs b/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItem.ImmutableTests.cs index 92bef3a5f..ccd474b4c 100644 --- a/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItem.ImmutableTests.cs +++ b/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItem.ImmutableTests.cs @@ -68,7 +68,7 @@ public async Task WhenEnumerableIsEmpty_ShouldFail() ImmutableArray subject = []; async Task Act() - => await That(subject).HasItem(_ => true).AtAnyIndex(); + => await That(subject).HasItem(_ => true); await That(Act).Throws() .WithMessage(""" @@ -89,7 +89,7 @@ public async Task WithMultipleFailures_ShouldIncludeCollectionOnlyOnce() async Task Act() => await That(subject).HasItem(_ => false).AtIndex(0).And.HasItem(_ => false).AtIndex(1).And .HasItem(_ => false) - .AtAnyIndex(); + ; await That(Act).Throws() .WithMessage(""" @@ -165,7 +165,7 @@ public async Task WhenEnumerableIsEmpty_ShouldFail(int expected) ImmutableArray subject = []; async Task Act() - => await That(subject).HasItem(expected).AtAnyIndex(); + => await That(subject).HasItem(expected); await That(Act).Throws() .WithMessage($""" @@ -185,7 +185,7 @@ public async Task WithMultipleFailures_ShouldIncludeCollectionOnlyOnce() async Task Act() => await That(subject).HasItem("d").AtIndex(0).And.HasItem("e").AtIndex(1).And.HasItem("f") - .AtAnyIndex(); + ; await That(Act).Throws() .WithMessage(""" @@ -212,7 +212,7 @@ public async Task WhenEquivalentItemIsFound_ShouldSucceed() MyClass expected = new(5); async Task Act() - => await That(subject).HasItem(expected).Equivalent().AtAnyIndex(); + => await That(subject).HasItem(expected).Equivalent(); await That(Act).DoesNotThrow(); } @@ -224,7 +224,7 @@ public async Task WhenEquivalentItemIsNotFound_ShouldFail() MyClass expected = new(4); async Task Act() - => await That(subject).HasItem(expected).Equivalent().AtAnyIndex(); + => await That(subject).HasItem(expected).Equivalent(); await That(Act).Throws() .WithMessage(""" @@ -291,7 +291,7 @@ public async Task WithAllDifferentComparer_ShouldFail() ImmutableArray subject = [..Factory.GetFibonacciNumbers(20),]; async Task Act() - => await That(subject).HasItem(1).Using(new AllDifferentComparer()).AtAnyIndex(); + => await That(subject).HasItem(1).Using(new AllDifferentComparer()); await That(Act).Throws() .WithMessage(""" @@ -322,7 +322,7 @@ public async Task WithAllEqualComparer_ShouldSucceed() ImmutableArray subject = [..Factory.GetFibonacciNumbers(20),]; async Task Act() - => await That(subject).HasItem(4).Using(new AllEqualComparer()).AtAnyIndex(); + => await That(subject).HasItem(4).Using(new AllEqualComparer()); await That(Act).DoesNotThrow(); } diff --git a/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItem.Tests.cs b/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItem.Tests.cs index 026cf5920..d26259065 100644 --- a/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItem.Tests.cs +++ b/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItem.Tests.cs @@ -18,7 +18,7 @@ public async Task DoesNotEnumerateTwice() ThrowWhenIteratingTwiceEnumerable subject = new(); async Task Act() - => await That(subject).HasItem(_ => true).AtAnyIndex() + => await That(subject).HasItem(_ => true) .And.HasItem(_ => true).AtIndex(0); await That(Act).DoesNotThrow(); @@ -30,7 +30,7 @@ public async Task DoesNotMaterializeEnumerable() IEnumerable subject = Factory.GetFibonacciNumbers(); async Task Act() - => await That(subject).HasItem(a => a == 5).AtAnyIndex(); + => await That(subject).HasItem(a => a == 5); await That(Act).DoesNotThrow(); } @@ -95,7 +95,7 @@ public async Task WhenEnumerableIsEmpty_ShouldFail() List subject = []; async Task Act() - => await That(subject).HasItem(_ => true).AtAnyIndex(); + => await That(subject).HasItem(_ => true); await That(Act).Throws() .WithMessage(""" @@ -114,7 +114,7 @@ public async Task WhenSubjectIsNull_WithAnyIndex_ShouldFail() IEnumerable? subject = null; async Task Act() - => await That(subject).HasItem(_ => true).AtAnyIndex(); + => await That(subject).HasItem(_ => true); await That(Act).Throws() .WithMessage(""" @@ -148,7 +148,7 @@ public async Task WithMultipleFailures_ShouldIncludeCollectionOnlyOnce() async Task Act() => await That(subject).HasItem(_ => false).AtIndex(0).And.HasItem(_ => false).AtIndex(1).And .HasItem(_ => false) - .AtAnyIndex(); + ; await That(Act).Throws() .WithMessage(""" @@ -174,7 +174,7 @@ public async Task DoesNotEnumerateTwice() ThrowWhenIteratingTwiceEnumerable subject = new(); async Task Act() - => await That(subject).HasItem(1).AtAnyIndex() + => await That(subject).HasItem(1) .And.HasItem(1).AtIndex(0); await That(Act).DoesNotThrow(); @@ -186,7 +186,7 @@ public async Task DoesNotMaterializeEnumerable() IEnumerable subject = Factory.GetFibonacciNumbers(); async Task Act() - => await That(subject).HasItem(5).AtAnyIndex(); + => await That(subject).HasItem(5); await That(Act).DoesNotThrow(); } @@ -261,7 +261,7 @@ public async Task WhenEnumerableIsEmpty_ShouldFail(int expected) List subject = []; async Task Act() - => await That(subject).HasItem(expected).AtAnyIndex(); + => await That(subject).HasItem(expected); await That(Act).Throws() .WithMessage($""" @@ -281,7 +281,7 @@ public async Task WhenSubjectIsNull_WithAnyIndex_ShouldFail() IEnumerable? subject = null; async Task Act() - => await That(subject).HasItem(expected).AtAnyIndex(); + => await That(subject).HasItem(expected); await That(Act).Throws() .WithMessage(""" @@ -315,7 +315,7 @@ public async Task WithMultipleFailures_ShouldIncludeCollectionOnlyOnce() async Task Act() => await That(subject).HasItem(4).AtIndex(0).And.HasItem(5).AtIndex(1).And.HasItem(6) - .AtAnyIndex(); + ; await That(Act).Throws() .WithMessage(""" @@ -635,7 +635,7 @@ public async Task WhenEnumerableIsEmpty_ShouldFail() List subject = []; async Task Act() - => await That(subject).HasItem("foo").AtAnyIndex(); + => await That(subject).HasItem("foo"); await That(Act).Throws() .WithMessage(""" @@ -654,7 +654,7 @@ public async Task WhenSubjectIsNull_WithAnyIndex_ShouldFail() IEnumerable? subject = null; async Task Act() - => await That(subject!).HasItem("foo").AtAnyIndex(); + => await That(subject!).HasItem("foo"); await That(Act).Throws() .WithMessage(""" @@ -721,7 +721,7 @@ public async Task WithMultipleFailures_ShouldIncludeCollectionOnlyOnce() async Task Act() => await That(subject).HasItem("d").AtIndex(0).And.HasItem("e").AtIndex(1).And.HasItem("f") - .AtAnyIndex(); + ; await That(Act).Throws() .WithMessage(""" @@ -748,7 +748,7 @@ public async Task WhenEquivalentItemIsFound_ShouldSucceed() MyClass expected = new(5); async Task Act() - => await That(subject).HasItem(expected).Equivalent().AtAnyIndex(); + => await That(subject).HasItem(expected).Equivalent(); await That(Act).DoesNotThrow(); } @@ -760,7 +760,7 @@ public async Task WhenEquivalentItemIsNotFound_ShouldFail() MyClass expected = new(4); async Task Act() - => await That(subject).HasItem(expected).Equivalent().AtAnyIndex(); + => await That(subject).HasItem(expected).Equivalent(); await That(Act).Throws() .WithMessage(""" @@ -827,7 +827,7 @@ public async Task WithAllDifferentComparer_ShouldFail() IEnumerable subject = Factory.GetFibonacciNumbers(20); async Task Act() - => await That(subject).HasItem(1).Using(new AllDifferentComparer()).AtAnyIndex(); + => await That(subject).HasItem(1).Using(new AllDifferentComparer()); await That(Act).Throws() .WithMessage(""" @@ -858,7 +858,7 @@ public async Task WithAllEqualComparer_ShouldSucceed() IEnumerable subject = Factory.GetFibonacciNumbers(20); async Task Act() - => await That(subject).HasItem(4).Using(new AllEqualComparer()).AtAnyIndex(); + => await That(subject).HasItem(4).Using(new AllEqualComparer()); await That(Act).DoesNotThrow(); } diff --git a/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItemThat.ImmutableTests.cs b/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItemThat.ImmutableTests.cs index d78a4f6ab..0f5eaa17b 100644 --- a/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItemThat.ImmutableTests.cs +++ b/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItemThat.ImmutableTests.cs @@ -66,7 +66,7 @@ public async Task WhenEnumerableIsEmpty_ShouldFail() ImmutableArray subject = []; async Task Act() - => await That(subject).HasItemThat(it => it.IsNotEqualTo(0)).AtAnyIndex(); + => await That(subject).HasItemThat(it => it.IsNotEqualTo(0)); await That(Act).Throws() .WithMessage(""" diff --git a/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItemThat.Tests.cs b/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItemThat.Tests.cs index 20eb16c8c..39aa4a841 100644 --- a/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItemThat.Tests.cs +++ b/Tests/aweXpect.Tests/Collections/ThatEnumerable.HasItemThat.Tests.cs @@ -16,7 +16,7 @@ public async Task DoesNotEnumerateTwice() ThrowWhenIteratingTwiceEnumerable subject = new(); async Task Act() - => await That(subject).HasItemThat(it => it.IsNotEqualTo(int.MinValue)).AtAnyIndex() + => await That(subject).HasItemThat(it => it.IsNotEqualTo(int.MinValue)) .And.HasItemThat(it => it.IsNotEqualTo(int.MinValue)).AtIndex(0); await That(Act).DoesNotThrow(); @@ -28,7 +28,7 @@ public async Task DoesNotMaterializeEnumerable() IEnumerable subject = Factory.GetFibonacciNumbers(); async Task Act() - => await That(subject).HasItemThat(it => it.IsEqualTo(5)).AtAnyIndex(); + => await That(subject).HasItemThat(it => it.IsEqualTo(5)); await That(Act).DoesNotThrow(); } @@ -93,7 +93,7 @@ public async Task WhenEnumerableIsEmpty_ShouldFail() List subject = []; async Task Act() - => await That(subject).HasItemThat(it => it.IsNotEqualTo(0)).AtAnyIndex(); + => await That(subject).HasItemThat(it => it.IsNotEqualTo(0)); await That(Act).Throws() .WithMessage(""" @@ -112,7 +112,7 @@ public async Task WhenSubjectIsNull_WithAnyIndex_ShouldFail() IEnumerable? subject = null; async Task Act() - => await That(subject).HasItemThat(it => it.IsNotEqualTo(0)).AtAnyIndex(); + => await That(subject).HasItemThat(it => it.IsNotEqualTo(0)); await That(Act).Throws() .WithMessage(""" From c749a9f3085ab4a9d4a8db3d515e19490e92073e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valentin=20Breu=C3=9F?= Date: Mon, 21 Jul 2025 21:47:18 +0200 Subject: [PATCH 2/2] Make `CollectionIndexOptions` more flexible --- .../Options/CollectionIndexOptions.cs | 85 ++++++++++++------- .../ThatAsyncEnumerable.HasItem.cs | 4 +- .../ThatAsyncEnumerable.HasItemThat.cs | 4 +- .../Collections/ThatEnumerable.HasItem.cs | 8 +- .../Collections/ThatEnumerable.HasItemThat.cs | 8 +- .../Expected/aweXpect.Core_net8.0.txt | 5 +- .../Expected/aweXpect.Core_netstandard2.0.txt | 5 +- 7 files changed, 72 insertions(+), 47 deletions(-) diff --git a/Source/aweXpect.Core/Options/CollectionIndexOptions.cs b/Source/aweXpect.Core/Options/CollectionIndexOptions.cs index 09a65b8c1..7f8e940d3 100644 --- a/Source/aweXpect.Core/Options/CollectionIndexOptions.cs +++ b/Source/aweXpect.Core/Options/CollectionIndexOptions.cs @@ -1,3 +1,5 @@ +using System; + namespace aweXpect.Options; /// @@ -5,8 +7,9 @@ namespace aweXpect.Options; /// public class CollectionIndexOptions { - private int? _maximum; - private int? _minimum; + private string _description = ""; + private Func _isIndexMatch = _ => true; + private bool _matchesOnlySingleIndex; /// /// Checks if the is in range. @@ -17,27 +20,14 @@ public class CollectionIndexOptions /// otherwise when the is not in range /// and will also not be in range for larger values. /// - public bool? IsIndexInRange(int index) - { - if (_maximum.HasValue && index > _maximum) - { - return false; - } - - if ((_minimum is null || index >= _minimum) && - (_maximum is null || index <= _maximum)) - { - return true; - } - - return null; - } + public bool? DoesIndexMatch(int index) + => _isIndexMatch.Invoke(index); /// /// Flag indicating, if only a single index is considered in range. /// - public bool HasOnlySingleIndex() - => _maximum == _minimum && _minimum is not null; + public bool MatchesOnlySingleIndex() + => _matchesOnlySingleIndex; /// /// Set the checked index to be in range between and . @@ -45,22 +35,55 @@ public bool HasOnlySingleIndex() /// When either parameter is set to , the corresponding range direction is unlimited. public void SetIndexRange(int? minimum, int? maximum) { - _minimum = minimum; - _maximum = maximum; + _isIndexMatch = index => + { + if (maximum.HasValue && index > maximum) + { + return false; + } + + if ((minimum is null || index >= minimum) && + (maximum is null || index <= maximum)) + { + return true; + } + + return null; + }; + _matchesOnlySingleIndex = maximum == minimum && minimum is not null; + if (minimum is null && maximum is null) + { + _description = ""; + } + else + { + _description = minimum == maximum + ? $" at index {minimum}" + : $" with index between {minimum} and {maximum}"; + } + } + + /// + /// Set the checked index to be a match depending on the function. + /// + /// The parameter specifies, if only a single index could match the + /// function, or if it could match multiple indices. + /// + /// + /// The is expected to return when the index matches, + /// when the index does not match, but could match for larger values and otherwise + /// , when it does not match and will not match for larger values. + /// + public void SetIndexMatch(Func isIndexMatch, bool matchesOnlySingleIndex, string description) + { + _isIndexMatch = isIndexMatch; + _matchesOnlySingleIndex = matchesOnlySingleIndex; + _description = description; } /// /// Returns the description of the . /// public string GetDescription() - { - if (_minimum is null && _maximum is null) - { - return ""; - } - - return _minimum == _maximum - ? $" at index {_minimum}" - : $" with index between {_minimum} and {_maximum}"; - } + => _description; } diff --git a/Source/aweXpect/That/Collections/ThatAsyncEnumerable.HasItem.cs b/Source/aweXpect/That/Collections/ThatAsyncEnumerable.HasItem.cs index 8c2d13506..aef86dbd1 100644 --- a/Source/aweXpect/That/Collections/ThatAsyncEnumerable.HasItem.cs +++ b/Source/aweXpect/That/Collections/ThatAsyncEnumerable.HasItem.cs @@ -108,7 +108,7 @@ public async Task IsMetBy(IAsyncEnumerable? actual, IEv await foreach (TItem item in materialized.WithCancellation(cancellationToken)) { index++; - bool? isIndexInRange = options.IsIndexInRange(index); + bool? isIndexInRange = options.DoesIndexMatch(index); if (isIndexInRange != true) { if (isIndexInRange == false) @@ -142,7 +142,7 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } else if (_hasIndex) { - if (options.HasOnlySingleIndex()) + if (options.MatchesOnlySingleIndex()) { stringBuilder.Append(it).Append(" had item "); Formatter.Format(stringBuilder, _actual); diff --git a/Source/aweXpect/That/Collections/ThatAsyncEnumerable.HasItemThat.cs b/Source/aweXpect/That/Collections/ThatAsyncEnumerable.HasItemThat.cs index 13f421784..3ad7183d6 100644 --- a/Source/aweXpect/That/Collections/ThatAsyncEnumerable.HasItemThat.cs +++ b/Source/aweXpect/That/Collections/ThatAsyncEnumerable.HasItemThat.cs @@ -75,7 +75,7 @@ public async Task IsMetBy(IAsyncEnumerable? actual, IEv await foreach (TItem item in materialized.WithCancellation(cancellationToken)) { index++; - bool? isIndexInRange = _options.IsIndexInRange(index); + bool? isIndexInRange = _options.DoesIndexMatch(index); if (isIndexInRange != true) { if (isIndexInRange == false) @@ -114,7 +114,7 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } else if (_hasIndex) { - if (_options.HasOnlySingleIndex()) + if (_options.MatchesOnlySingleIndex()) { stringBuilder.Append(_it).Append(" had item "); Formatter.Format(stringBuilder, _actual); diff --git a/Source/aweXpect/That/Collections/ThatEnumerable.HasItem.cs b/Source/aweXpect/That/Collections/ThatEnumerable.HasItem.cs index bbef4fa7d..a29e25082 100644 --- a/Source/aweXpect/That/Collections/ThatEnumerable.HasItem.cs +++ b/Source/aweXpect/That/Collections/ThatEnumerable.HasItem.cs @@ -217,7 +217,7 @@ public ConstraintResult IsMetBy(IEnumerable? actual, IEvaluationContext c foreach (TItem item in materialized) { index++; - bool? isIndexInRange = options.IsIndexInRange(index); + bool? isIndexInRange = options.DoesIndexMatch(index); if (isIndexInRange != true) { if (isIndexInRange == false) @@ -251,7 +251,7 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } else if (_hasIndex) { - if (options.HasOnlySingleIndex()) + if (options.MatchesOnlySingleIndex()) { stringBuilder.Append(it).Append(" had item "); Formatter.Format(stringBuilder, _actual); @@ -321,7 +321,7 @@ public ConstraintResult IsMetBy(TEnumerable actual, IEvaluationContext context) foreach (TItem item in materialized.Cast()) { index++; - bool? isIndexInRange = options.IsIndexInRange(index); + bool? isIndexInRange = options.DoesIndexMatch(index); if (isIndexInRange != true) { if (isIndexInRange == false) @@ -354,7 +354,7 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } else if (_actual is not null) { - if (options.HasOnlySingleIndex()) + if (options.MatchesOnlySingleIndex()) { stringBuilder.Append(it).Append(" had item "); Formatter.Format(stringBuilder, _actual); diff --git a/Source/aweXpect/That/Collections/ThatEnumerable.HasItemThat.cs b/Source/aweXpect/That/Collections/ThatEnumerable.HasItemThat.cs index e67ea3753..32f9d7c3a 100644 --- a/Source/aweXpect/That/Collections/ThatEnumerable.HasItemThat.cs +++ b/Source/aweXpect/That/Collections/ThatEnumerable.HasItemThat.cs @@ -96,7 +96,7 @@ public async Task IsMetBy(IEnumerable? actual, IEvaluat foreach (TItem item in materialized) { index++; - bool? isIndexInRange = _options.IsIndexInRange(index); + bool? isIndexInRange = _options.DoesIndexMatch(index); if (isIndexInRange != true) { if (isIndexInRange == false) @@ -135,7 +135,7 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } else if (_hasIndex) { - if (_options.HasOnlySingleIndex()) + if (_options.MatchesOnlySingleIndex()) { stringBuilder.Append(_it).Append(" had item "); Formatter.Format(stringBuilder, _actual); @@ -222,7 +222,7 @@ public async Task IsMetBy(TEnumerable? actual, IEvaluationCont foreach (TItem item in materialized.Cast()) { index++; - bool? isIndexInRange = _options.IsIndexInRange(index); + bool? isIndexInRange = _options.DoesIndexMatch(index); if (isIndexInRange != true) { if (isIndexInRange == false) @@ -260,7 +260,7 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } else if (_actual is not null) { - if (_options.HasOnlySingleIndex()) + if (_options.MatchesOnlySingleIndex()) { stringBuilder.Append(_it).Append(" had item "); 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 2cd09ee78..a39b587e6 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 @@ -753,9 +753,10 @@ namespace aweXpect.Options public class CollectionIndexOptions { public CollectionIndexOptions() { } + public bool? DoesIndexMatch(int index) { } public string GetDescription() { } - public bool HasOnlySingleIndex() { } - public bool? IsIndexInRange(int index) { } + public bool MatchesOnlySingleIndex() { } + public void SetIndexMatch(System.Func isIndexMatch, bool matchesOnlySingleIndex, string description) { } public void SetIndexRange(int? minimum, int? maximum) { } } public class CollectionMatchOptions 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 a9b716c33..c297d3ae4 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 @@ -736,9 +736,10 @@ namespace aweXpect.Options public class CollectionIndexOptions { public CollectionIndexOptions() { } + public bool? DoesIndexMatch(int index) { } public string GetDescription() { } - public bool HasOnlySingleIndex() { } - public bool? IsIndexInRange(int index) { } + public bool MatchesOnlySingleIndex() { } + public void SetIndexMatch(System.Func isIndexMatch, bool matchesOnlySingleIndex, string description) { } public void SetIndexRange(int? minimum, int? maximum) { } } public class CollectionMatchOptions