Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion Docs/pages/docs/expectations/07-collections.md
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,16 @@ You can verify that the collection contains an item that satisfies the expectati
IEnumerable<string> values = ["0th item", "1st item", "2nd item", "3rd item"];

await Expect.That(values).HasItem("1st item").AtIndex(1);
await Expect.That(values).HasItem(a => a.StartsWith("2nd")).AtAnyIndex();
await Expect.That(values).HasItem(it => it.StartsWith("2nd")).AtAnyIndex();
```

You can also use expectations on the individual items.

```csharp
IEnumerable<string> 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();
```

*Note: The same expectation works also for `IAsyncEnumerable<T>`.*
Expand Down
2 changes: 1 addition & 1 deletion Source/aweXpect.Core/Core/Nodes/AndNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public override void SetReason(BecauseReason becauseReason)
/// <inheritdoc />
public override void AppendExpectation(StringBuilder stringBuilder, string? indentation = null)
{
foreach (Node node in _nodes.Select(n => n.Item2))
foreach (Node node in _nodes.Select(n => n.Item2).Where(n => n != Current))
{
node.AppendExpectation(stringBuilder, indentation);
stringBuilder.Append(DefaultSeparator);
Expand Down
2 changes: 1 addition & 1 deletion Source/aweXpect.Core/Core/Nodes/OrNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public override void SetReason(BecauseReason becauseReason)
/// <inheritdoc />
public override void AppendExpectation(StringBuilder stringBuilder, string? indentation = null)
{
foreach (Node node in _nodes.Select(n => n.Item2))
foreach (Node node in _nodes.Select(n => n.Item2).Where(n => n != Current))
{
node.AppendExpectation(stringBuilder, indentation);
stringBuilder.Append(DefaultSeparator);
Expand Down
160 changes: 160 additions & 0 deletions Source/aweXpect/That/Collections/ThatAsyncEnumerable.HasItemThat.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#if NET8_0_OR_GREATER
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using aweXpect.Core;
using aweXpect.Core.Constraints;
using aweXpect.Core.EvaluationContext;
using aweXpect.Helpers;
using aweXpect.Options;
using aweXpect.Results;

// ReSharper disable PossibleMultipleEnumeration

namespace aweXpect;

public static partial class ThatAsyncEnumerable
{
/// <summary>
/// Verifies that the collection has an item that complies with the <paramref name="expectations" />…
/// </summary>
public static HasItemResult<IAsyncEnumerable<TItem>?> HasItemThat<TItem>(
this IThat<IAsyncEnumerable<TItem>?> source, Action<IThat<TItem>> expectations)
{
CollectionIndexOptions indexOptions = new();
ExpectationBuilder expectationBuilder = source.Get().ExpectationBuilder;
return new HasItemResult<IAsyncEnumerable<TItem>?>(
expectationBuilder.AddConstraint((it, grammars)
=> new HasItemThatConstraint<TItem>(expectationBuilder, it, grammars, expectations, indexOptions)),
source,
indexOptions);
}

private sealed class HasItemThatConstraint<TItem> : ConstraintResult.WithValue<IAsyncEnumerable<TItem>?>,
IAsyncContextConstraint<IAsyncEnumerable<TItem>?>
{
private readonly ExpectationBuilder _expectationBuilder;
private readonly string _it;
private readonly ManualExpectationBuilder<TItem> _itemExpectationBuilder;
private readonly CollectionIndexOptions _options;
private TItem? _actual;
private bool _hasIndex;

public HasItemThatConstraint(ExpectationBuilder expectationBuilder,
string it,
ExpectationGrammars grammars,
Action<IThat<TItem>> expectations,
CollectionIndexOptions options) : base(grammars)
{
_expectationBuilder = expectationBuilder;
_it = it;
_options = options;

_itemExpectationBuilder = new ManualExpectationBuilder<TItem>(_expectationBuilder, Grammars);
expectations.Invoke(new ThatSubject<TItem>(_itemExpectationBuilder));
}

public async Task<ConstraintResult> IsMetBy(IAsyncEnumerable<TItem>? actual, IEvaluationContext context,
CancellationToken cancellationToken)
{
Actual = actual;
if (actual is null)
{
Outcome = Outcome.Failure;
return this;
}

IAsyncEnumerable<TItem> materialized =
context.UseMaterializedAsyncEnumerable<TItem, IAsyncEnumerable<TItem>>(actual);
await _expectationBuilder.AddCollectionContext(materialized as IMaterializedEnumerable<TItem>);
_hasIndex = false;
Outcome = Outcome.Failure;

int index = -1;
await foreach (TItem item in materialized.WithCancellation(cancellationToken))
{
index++;
bool? isIndexInRange = _options.IsIndexInRange(index);
if (isIndexInRange != true)
{
if (isIndexInRange == false)
{
break;
}

continue;
}

_hasIndex = true;
_actual = item;
ConstraintResult isMatch = await _itemExpectationBuilder.IsMetBy(item, context, cancellationToken);
Outcome = isMatch.Outcome;
if (Outcome == Outcome.Success)
{
break;
}
}

return this;
}

protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null)
{
stringBuilder.Append("has item that ");
_itemExpectationBuilder.AppendExpectation(stringBuilder, indentation);
stringBuilder.Append(_options.GetDescription());
}

protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null)
{
if (Actual is null)
{
stringBuilder.ItWasNull(_it);
}
else if (_hasIndex)
{
if (_options.HasOnlySingleIndex())
{
stringBuilder.Append(_it).Append(" had item ");
Formatter.Format(stringBuilder, _actual);
stringBuilder.Append(_options.GetDescription());
}
else
{
string optionDescription = _options.GetDescription();
if (string.IsNullOrEmpty(optionDescription))
{
optionDescription = " at any index";
}

stringBuilder.Append(_it).Append(" did not match").Append(optionDescription);
}
}
else
{
stringBuilder.Append(_it).Append(" did not contain any item").Append(_options.GetDescription());
}
}

protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null)
{
stringBuilder.Append("does not have item that ");
_itemExpectationBuilder.AppendExpectation(stringBuilder, indentation);
stringBuilder.Append(_options.GetDescription());
}

protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null)
{
if (_actual is null)
{
stringBuilder.ItWasNull(_it);
}
else
{
stringBuilder.Append(_it).Append(" did");
}
}
}
}
#endif
Loading
Loading