Skip to content

Commit 02df387

Browse files
authored
fix: HasItem without parameters does not make sense (#719)
This PR removes the ability to call `HasItem()` without any parameters or conditions by making the `HasItemWithConditionResult` class non-chainable after construction. The purpose is to prevent compilation of semantically meaningless assertions like `collection.HasItem()` without any further restrictions. - Changes the inheritance hierarchy of `HasItemWithConditionResult` to remove direct chaining capabilities - Updates method return types to return `HasItemResult<TCollection>` instead of self-referencing types - Adjusts type parameters in API signatures to include nullability annotations
1 parent df0c03b commit 02df387

4 files changed

Lines changed: 34 additions & 38 deletions

File tree

Source/aweXpect/Results/HasItemWithConditionResult.cs

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,17 @@ namespace aweXpect.Results;
1313
/// <seealso cref="ExpectationResult{TType,TSelf}" />
1414
/// </remarks>
1515
public class HasItemWithConditionResult<TCollection, TItem>
16-
: HasItemResult<TCollection?>,
17-
IOptionsProvider<PredicateOptions<TItem>>
16+
: IOptionsProvider<PredicateOptions<TItem>>
1817
{
1918
private readonly CollectionIndexOptions _collectionIndexOptions;
2019
private readonly ExpectationBuilder _expectationBuilder;
2120
private readonly PredicateOptions<TItem> _options;
22-
private readonly IThat<TCollection?> _subject;
21+
private readonly IThat<TCollection> _subject;
2322

2423
internal HasItemWithConditionResult(ExpectationBuilder expectationBuilder,
25-
IThat<TCollection?> subject,
24+
IThat<TCollection> subject,
2625
CollectionIndexOptions collectionIndexOptions,
2726
PredicateOptions<TItem> options)
28-
: base(expectationBuilder, subject, collectionIndexOptions)
2927
{
3028
_expectationBuilder = expectationBuilder;
3129
_subject = subject;
@@ -39,62 +37,60 @@ internal HasItemWithConditionResult(ExpectationBuilder expectationBuilder,
3937
/// <summary>
4038
/// …that satisfies the <paramref name="predicate" />.
4139
/// </summary>
42-
public HasItemWithConditionResult<TCollection, TItem> Matching(Func<TItem, bool> predicate,
40+
public HasItemResult<TCollection> Matching(Func<TItem, bool> predicate,
4341
[CallerArgumentExpression("predicate")]
4442
string doNotPopulateThisValue = "")
4543
{
4644
predicate.ThrowIfNull();
4745
_options.SetPredicate(predicate,
4846
$"matching {doNotPopulateThisValue}");
49-
return this;
47+
return new HasItemResult<TCollection>(_expectationBuilder, _subject, _collectionIndexOptions);
5048
}
5149

5250
/// <summary>
5351
/// …of type <typeparamref name="T" />.
5452
/// </summary>
55-
public HasItemWithConditionResult<TCollection, T> Matching<T>()
53+
public HasItemResult<TCollection> Matching<T>()
5654
{
5755
_options.SetPredicate(item => item is T,
5856
$"of type {Formatter.Format(typeof(T))}");
59-
return Cast<T>();
57+
return new HasItemResult<TCollection>(_expectationBuilder, _subject, _collectionIndexOptions);
6058
}
6159

6260
/// <summary>
6361
/// …of type <typeparamref name="T" /> that satisfies the <paramref name="predicate" />.
6462
/// </summary>
65-
public HasItemWithConditionResult<TCollection, T> Matching<T>(Func<T, bool> predicate,
63+
public HasItemResult<TCollection> Matching<T>(Func<T, bool> predicate,
6664
[CallerArgumentExpression("predicate")]
6765
string doNotPopulateThisValue = "")
6866
{
6967
predicate.ThrowIfNull();
7068
_options.SetPredicate(item => item is T typed && predicate(typed),
7169
$"of type {Formatter.Format(typeof(T))} matching {doNotPopulateThisValue}");
72-
return Cast<T>();
70+
return new HasItemResult<TCollection>(_expectationBuilder, _subject, _collectionIndexOptions);
7371
}
7472

7573
/// <summary>
7674
/// …of type <typeparamref name="T" />.
7775
/// </summary>
78-
public HasItemWithConditionResult<TCollection, T> MatchingExactly<T>()
76+
public HasItemResult<TCollection> MatchingExactly<T>()
7977
{
8078
_options.SetPredicate(item => item is T && item.GetType() == typeof(T),
8179
$"exactly of type {Formatter.Format(typeof(T))}");
82-
return Cast<T>();
80+
81+
return new HasItemResult<TCollection>(_expectationBuilder, _subject, _collectionIndexOptions);
8382
}
8483

8584
/// <summary>
8685
/// …of type <typeparamref name="T" /> that satisfies the <paramref name="predicate" />.
8786
/// </summary>
88-
public HasItemWithConditionResult<TCollection, T> MatchingExactly<T>(Func<T, bool> predicate,
87+
public HasItemResult<TCollection> MatchingExactly<T>(Func<T, bool> predicate,
8988
[CallerArgumentExpression("predicate")]
9089
string doNotPopulateThisValue = "")
9190
{
9291
predicate.ThrowIfNull();
9392
_options.SetPredicate(item => item is T typed && item.GetType() == typeof(T) && predicate(typed),
9493
$"exactly of type {Formatter.Format(typeof(T))} matching {doNotPopulateThisValue}");
95-
return Cast<T>();
94+
return new HasItemResult<TCollection>(_expectationBuilder, _subject, _collectionIndexOptions);
9695
}
97-
98-
private HasItemWithConditionResult<TCollection, T> Cast<T>()
99-
=> new(_expectationBuilder, _subject, _collectionIndexOptions, new PredicateOptions<T>());
10096
}

Source/aweXpect/That/Collections/ThatEnumerable.HasItem.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ public static partial class ThatEnumerable
2222
/// <summary>
2323
/// Verifies that the collection has an item…
2424
/// </summary>
25-
public static HasItemWithConditionResult<IEnumerable<TItem>, TItem> HasItem<TItem>(
25+
public static HasItemWithConditionResult<IEnumerable<TItem>?, TItem> HasItem<TItem>(
2626
this IThat<IEnumerable<TItem>?> source)
2727
{
2828
CollectionIndexOptions indexOptions = new();
2929
PredicateOptions<TItem> options = new();
3030
ExpectationBuilder expectationBuilder = source.Get().ExpectationBuilder;
31-
return new HasItemWithConditionResult<IEnumerable<TItem>, TItem>(
31+
return new HasItemWithConditionResult<IEnumerable<TItem>?, TItem>(
3232
expectationBuilder.AddConstraint((it, grammars)
3333
=> new HasItemConstraint<TItem>(expectationBuilder, it, grammars,
3434
x => options.Matches(x),
@@ -100,13 +100,13 @@ public static HasItemWithConditionResult<IEnumerable<TItem>, TItem> HasItem<TIte
100100
/// <summary>
101101
/// Verifies that the collection has an item…
102102
/// </summary>
103-
public static HasItemWithConditionResult<IEnumerable, object?> HasItem(
103+
public static HasItemWithConditionResult<IEnumerable?, object?> HasItem(
104104
this IThat<IEnumerable?> source)
105105
{
106106
CollectionIndexOptions indexOptions = new();
107107
PredicateOptions<object?> options = new();
108108
ExpectationBuilder expectationBuilder = source.Get().ExpectationBuilder;
109-
return new HasItemWithConditionResult<IEnumerable, object?>(
109+
return new HasItemWithConditionResult<IEnumerable?, object?>(
110110
expectationBuilder.AddConstraint((it, grammars)
111111
=> new HasItemForEnumerableConstraint<IEnumerable, object?>(expectationBuilder, it, grammars, x => options.Matches(x), options.GetDescription, indexOptions)),
112112
source,

Tests/aweXpect.Api.Tests/Expected/aweXpect_net8.0.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -484,12 +484,12 @@ namespace aweXpect
484484
public static aweXpect.Results.AndOrResult<System.Collections.Generic.IEnumerable<TItem>, aweXpect.Core.IThat<System.Collections.Generic.IEnumerable<TItem>?>> HasCount<TItem>(this aweXpect.Core.IThat<System.Collections.Generic.IEnumerable<TItem>?> subject, int expected) { }
485485
public static aweXpect.Results.AndOrResult<System.Collections.Immutable.ImmutableArray<TItem>, aweXpect.Core.IThat<System.Collections.Immutable.ImmutableArray<TItem>>> HasCount<TItem>(this aweXpect.Core.IThat<System.Collections.Immutable.ImmutableArray<TItem>> subject, int expected) { }
486486
public static aweXpect.Results.AndOrResult<TItem[], aweXpect.Core.IThat<TItem[]?>> HasCount<TItem>(this aweXpect.Core.IThat<TItem[]?> subject, int expected) { }
487-
public static aweXpect.Results.HasItemWithConditionResult<System.Collections.IEnumerable, object?> HasItem(this aweXpect.Core.IThat<System.Collections.IEnumerable?> source) { }
487+
public static aweXpect.Results.HasItemWithConditionResult<System.Collections.IEnumerable?, object?> HasItem(this aweXpect.Core.IThat<System.Collections.IEnumerable?> source) { }
488488
public static aweXpect.Results.StringHasItemResult<System.Collections.Generic.IEnumerable<string?>?> HasItem(this aweXpect.Core.IThat<System.Collections.Generic.IEnumerable<string?>?> source, string? expected) { }
489489
public static aweXpect.Results.ObjectHasItemResult<System.Collections.IEnumerable, object?> HasItem(this aweXpect.Core.IThat<System.Collections.IEnumerable> source, object? expected) { }
490490
public static aweXpect.Results.StringHasItemResult<System.Collections.Immutable.ImmutableArray<string?>> HasItem(this aweXpect.Core.IThat<System.Collections.Immutable.ImmutableArray<string?>> source, string? expected) { }
491491
public static aweXpect.Results.HasItemResult<System.Collections.IEnumerable> HasItem(this aweXpect.Core.IThat<System.Collections.IEnumerable> source, System.Func<object?, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
492-
public static aweXpect.Results.HasItemWithConditionResult<System.Collections.Generic.IEnumerable<TItem>, TItem> HasItem<TItem>(this aweXpect.Core.IThat<System.Collections.Generic.IEnumerable<TItem>?> source) { }
492+
public static aweXpect.Results.HasItemWithConditionResult<System.Collections.Generic.IEnumerable<TItem>?, TItem> HasItem<TItem>(this aweXpect.Core.IThat<System.Collections.Generic.IEnumerable<TItem>?> source) { }
493493
public static aweXpect.Results.HasItemWithConditionResult<System.Collections.Immutable.ImmutableArray<TItem>, TItem> HasItem<TItem>(this aweXpect.Core.IThat<System.Collections.Immutable.ImmutableArray<TItem>> source) { }
494494
public static aweXpect.Results.ObjectHasItemResult<System.Collections.Generic.IEnumerable<TItem>?, TItem> HasItem<TItem>(this aweXpect.Core.IThat<System.Collections.Generic.IEnumerable<TItem>?> source, TItem expected) { }
495495
public static aweXpect.Results.ObjectHasItemResult<System.Collections.Immutable.ImmutableArray<TItem>, TItem> HasItem<TItem>(this aweXpect.Core.IThat<System.Collections.Immutable.ImmutableArray<TItem>> source, TItem expected) { }
@@ -1254,13 +1254,13 @@ namespace aweXpect.Results
12541254
aweXpect.Results.EventTriggerResult<TSubject> WithParameter<TParameter>(string expression, int? position, System.Func<TParameter, bool> predicate);
12551255
}
12561256
}
1257-
public class HasItemWithConditionResult<TCollection, TItem> : aweXpect.Results.HasItemResult<TCollection?>, aweXpect.Core.IOptionsProvider<aweXpect.Options.PredicateOptions<TItem>>
1257+
public class HasItemWithConditionResult<TCollection, TItem> : aweXpect.Core.IOptionsProvider<aweXpect.Options.PredicateOptions<TItem>>
12581258
{
1259-
public aweXpect.Results.HasItemWithConditionResult<TCollection, TItem> Matching(System.Func<TItem, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
1260-
public aweXpect.Results.HasItemWithConditionResult<TCollection, T> Matching<T>() { }
1261-
public aweXpect.Results.HasItemWithConditionResult<TCollection, T> Matching<T>(System.Func<T, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
1262-
public aweXpect.Results.HasItemWithConditionResult<TCollection, T> MatchingExactly<T>() { }
1263-
public aweXpect.Results.HasItemWithConditionResult<TCollection, T> MatchingExactly<T>(System.Func<T, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
1259+
public aweXpect.Results.HasItemResult<TCollection> Matching(System.Func<TItem, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
1260+
public aweXpect.Results.HasItemResult<TCollection> Matching<T>() { }
1261+
public aweXpect.Results.HasItemResult<TCollection> Matching<T>(System.Func<T, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
1262+
public aweXpect.Results.HasItemResult<TCollection> MatchingExactly<T>() { }
1263+
public aweXpect.Results.HasItemResult<TCollection> MatchingExactly<T>(System.Func<T, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
12641264
}
12651265
public class IsParsableResult<TType> : aweXpect.Results.AndOrResult<string?, aweXpect.Core.IThat<string?>>
12661266
where TType : System.IParsable<TType>

Tests/aweXpect.Api.Tests/Expected/aweXpect_netstandard2.0.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -291,11 +291,11 @@ namespace aweXpect
291291
public static aweXpect.CollectionCountResult<aweXpect.Results.AndOrResult<TItem[], aweXpect.Core.IThat<TItem[]?>>> HasCount<TItem>(this aweXpect.Core.IThat<TItem[]?> subject) { }
292292
public static aweXpect.Results.AndOrResult<System.Collections.Generic.IEnumerable<TItem>, aweXpect.Core.IThat<System.Collections.Generic.IEnumerable<TItem>?>> HasCount<TItem>(this aweXpect.Core.IThat<System.Collections.Generic.IEnumerable<TItem>?> subject, int expected) { }
293293
public static aweXpect.Results.AndOrResult<TItem[], aweXpect.Core.IThat<TItem[]?>> HasCount<TItem>(this aweXpect.Core.IThat<TItem[]?> subject, int expected) { }
294-
public static aweXpect.Results.HasItemWithConditionResult<System.Collections.IEnumerable, object?> HasItem(this aweXpect.Core.IThat<System.Collections.IEnumerable?> source) { }
294+
public static aweXpect.Results.HasItemWithConditionResult<System.Collections.IEnumerable?, object?> HasItem(this aweXpect.Core.IThat<System.Collections.IEnumerable?> source) { }
295295
public static aweXpect.Results.StringHasItemResult<System.Collections.Generic.IEnumerable<string?>?> HasItem(this aweXpect.Core.IThat<System.Collections.Generic.IEnumerable<string?>?> source, string? expected) { }
296296
public static aweXpect.Results.ObjectHasItemResult<System.Collections.IEnumerable, object?> HasItem(this aweXpect.Core.IThat<System.Collections.IEnumerable> source, object? expected) { }
297297
public static aweXpect.Results.HasItemResult<System.Collections.IEnumerable> HasItem(this aweXpect.Core.IThat<System.Collections.IEnumerable> source, System.Func<object?, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
298-
public static aweXpect.Results.HasItemWithConditionResult<System.Collections.Generic.IEnumerable<TItem>, TItem> HasItem<TItem>(this aweXpect.Core.IThat<System.Collections.Generic.IEnumerable<TItem>?> source) { }
298+
public static aweXpect.Results.HasItemWithConditionResult<System.Collections.Generic.IEnumerable<TItem>?, TItem> HasItem<TItem>(this aweXpect.Core.IThat<System.Collections.Generic.IEnumerable<TItem>?> source) { }
299299
public static aweXpect.Results.ObjectHasItemResult<System.Collections.Generic.IEnumerable<TItem>?, TItem> HasItem<TItem>(this aweXpect.Core.IThat<System.Collections.Generic.IEnumerable<TItem>?> source, TItem expected) { }
300300
public static aweXpect.Results.HasItemResult<System.Collections.Generic.IEnumerable<TItem>?> HasItem<TItem>(this aweXpect.Core.IThat<System.Collections.Generic.IEnumerable<TItem>?> source, System.Func<TItem, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
301301
public static aweXpect.Results.HasItemResult<System.Collections.Generic.IEnumerable<TItem>?> HasItemThat<TItem>(this aweXpect.Core.IThat<System.Collections.Generic.IEnumerable<TItem>?> source, System.Action<aweXpect.Core.IThat<TItem>> expectations) { }
@@ -1223,13 +1223,13 @@ namespace aweXpect.Results
12231223
aweXpect.Results.EventTriggerResult<TSubject> WithParameter<TParameter>(string expression, int? position, System.Func<TParameter, bool> predicate);
12241224
}
12251225
}
1226-
public class HasItemWithConditionResult<TCollection, TItem> : aweXpect.Results.HasItemResult<TCollection?>, aweXpect.Core.IOptionsProvider<aweXpect.Options.PredicateOptions<TItem>>
1226+
public class HasItemWithConditionResult<TCollection, TItem> : aweXpect.Core.IOptionsProvider<aweXpect.Options.PredicateOptions<TItem>>
12271227
{
1228-
public aweXpect.Results.HasItemWithConditionResult<TCollection, TItem> Matching(System.Func<TItem, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
1229-
public aweXpect.Results.HasItemWithConditionResult<TCollection, T> Matching<T>() { }
1230-
public aweXpect.Results.HasItemWithConditionResult<TCollection, T> Matching<T>(System.Func<T, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
1231-
public aweXpect.Results.HasItemWithConditionResult<TCollection, T> MatchingExactly<T>() { }
1232-
public aweXpect.Results.HasItemWithConditionResult<TCollection, T> MatchingExactly<T>(System.Func<T, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
1228+
public aweXpect.Results.HasItemResult<TCollection> Matching(System.Func<TItem, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
1229+
public aweXpect.Results.HasItemResult<TCollection> Matching<T>() { }
1230+
public aweXpect.Results.HasItemResult<TCollection> Matching<T>(System.Func<T, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
1231+
public aweXpect.Results.HasItemResult<TCollection> MatchingExactly<T>() { }
1232+
public aweXpect.Results.HasItemResult<TCollection> MatchingExactly<T>(System.Func<T, bool> predicate, [System.Runtime.CompilerServices.CallerArgumentExpression("predicate")] string doNotPopulateThisValue = "") { }
12331233
}
12341234
public class ObjectCollectionBeContainedInResult<TType, TThat, TItem> : aweXpect.Results.ObjectCollectionMatchResult<TType, TThat, TItem>
12351235
{

0 commit comments

Comments
 (0)