Skip to content

Commit

Permalink
Collection overloads for asserting on ImmutableArrays (#2043)
Browse files Browse the repository at this point in the history
* Collection overloads for asserting on ImmutableArrays

* chore: add System.Collections.Immutable package reference and clean up project files

* Fixes

* Compilation fixes
  • Loading branch information
thomhurst authored Mar 9, 2025
1 parent 59e44ab commit 2b6b558
Show file tree
Hide file tree
Showing 14 changed files with 276 additions and 80 deletions.
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
<PackageVersion Include="Sourcy.DotNet" Version="0.7.6" />
<PackageVersion Include="Sourcy.Git" Version="0.7.6" />
<PackageVersion Include="StreamJsonRpc" Version="2.21.10" />
<PackageVersion Include="System.Collections.Immutable" Version="9.0.2" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.Text.Json" Version="9.0.2" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.0" />
Expand Down
17 changes: 17 additions & 0 deletions TUnit.Assertions.Tests/Bugs/Tests1917.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Collections.Immutable;
using TUnit.Assertions.Enums;

namespace TUnit.Assertions.Tests.Bugs;

public class Tests1917
{
[Test]
public async Task Immutable_Array_Has_Enumerable_Methods()
{
var array = ImmutableArray<string>.Empty;
var list = ImmutableList<string>.Empty;

await Assert.That(array).IsEmpty();
await Assert.That(list).IsEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#nullable disable

using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using TUnit.Assertions.AssertConditions.Collections;
Expand Down Expand Up @@ -97,7 +98,7 @@ public static InvokableValueAssertionBuilder<IEnumerable<TInner>> IsOrderedBy<TI
[CallerArgumentExpression(nameof(comparer))] string doNotPopulateThisValue2 = null)
{
return valueSource.RegisterAssertion(
new EnumerableOrderedByAssertCondition<TInner, TComparisonItem>(comparer, comparisonItemSelector, Order.Ascending), [doNotPopulateThisValue, doNotPopulateThisValue2]);
new EnumerableOrderedByAssertCondition<IEnumerable<TInner>, TInner, TComparisonItem>(comparer, comparisonItemSelector, Order.Ascending), [doNotPopulateThisValue, doNotPopulateThisValue2]);
}

public static InvokableValueAssertionBuilder<IEnumerable<TInner>> IsOrderedByDescending<TInner, TComparisonItem>(
Expand All @@ -108,11 +109,11 @@ public static InvokableValueAssertionBuilder<IEnumerable<TInner>> IsOrderedByDes
[CallerArgumentExpression(nameof(comparer))] string doNotPopulateThisValue2 = null)
{
return valueSource.RegisterAssertion(
new EnumerableOrderedByAssertCondition<TInner, TComparisonItem>(comparer, comparisonItemSelector, Order.Descending), [doNotPopulateThisValue, doNotPopulateThisValue2]);
new EnumerableOrderedByAssertCondition<IEnumerable<TInner>, TInner, TComparisonItem>(comparer, comparisonItemSelector, Order.Descending), [doNotPopulateThisValue, doNotPopulateThisValue2]);
}

public static InvokableValueAssertionBuilder<IEnumerable<TInner>> IsEmpty<TInner>(this IValueSource<IEnumerable<TInner>> valueSource)
{
return valueSource.RegisterAssertion(new EnumerableCountEqualToExpectedValueAssertCondition<TInner>(0), []);
return valueSource.RegisterAssertion(new EnumerableCountEqualToExpectedValueAssertCondition<IEnumerable<TInner>, TInner>(0), []);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public static InvokableValueAssertionBuilder<TActual> IsNotEquivalentTo<TActual,

public static InvokableValueAssertionBuilder<IEnumerable<TInner>> IsNotEmpty<TInner>(this IValueSource<IEnumerable<TInner>> valueSource)
{
return valueSource.RegisterAssertion(new EnumerableCountNotEqualToExpectedValueAssertCondition<TInner>(0)
return valueSource.RegisterAssertion(new EnumerableCountNotEqualToExpectedValueAssertCondition<IEnumerable<TInner>, TInner>(0)
, []);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

namespace TUnit.Assertions.AssertConditions.Collections;

public class EnumerableCountEqualToExpectedValueAssertCondition<TInner>(int expected)
: ExpectedValueAssertCondition<IEnumerable<TInner>, int>(expected)
public class EnumerableCountEqualToExpectedValueAssertCondition<TActual, TInner>(int expected)
: ExpectedValueAssertCondition<TActual, int>(expected) where TActual : IEnumerable<TInner>
{
protected override string GetExpectation() => $"to have a count of {expected}";

protected override ValueTask<AssertionResult> GetResult(IEnumerable<TInner>? actualValue, int count)
protected override ValueTask<AssertionResult> GetResult(TActual? actualValue, int count)
{
var actualCount = GetCount(actualValue);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

namespace TUnit.Assertions.AssertConditions.Collections;

public class EnumerableCountNotEqualToExpectedValueAssertCondition<TInner>(int expected)
: ExpectedValueAssertCondition<IEnumerable<TInner>, int>(expected)
public class EnumerableCountNotEqualToExpectedValueAssertCondition<TActual, TInner>(int expected)
: ExpectedValueAssertCondition<TActual, int>(expected) where TActual : IEnumerable<TInner>
{
protected override string GetExpectation() => $"to have a count different to {expected}";

protected override ValueTask<AssertionResult> GetResult(IEnumerable<TInner>? actualValue, int count)
protected override ValueTask<AssertionResult> GetResult(TActual? actualValue, int count)
{
var actualCount = GetCount(actualValue);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
namespace TUnit.Assertions.AssertConditions.Collections;

public class EnumerableDistinctItemsExpectedValueAssertCondition<TInner>(IEqualityComparer<TInner?>? equalityComparer)
: BaseAssertCondition<IEnumerable<TInner>>
public class EnumerableDistinctItemsExpectedValueAssertCondition<TActual, TInner>(IEqualityComparer<TInner?>? equalityComparer)
: BaseAssertCondition<TActual> where TActual : IEnumerable<TInner>
{
protected override string GetExpectation() => "items to be distinct";

protected override ValueTask<AssertionResult> GetResult(
IEnumerable<TInner>? actualValue, Exception? exception,
TActual? actualValue, Exception? exception,
AssertionMetadata assertionMetadata
)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@

namespace TUnit.Assertions.AssertConditions.Collections;

public class EnumerableOrderedByAssertCondition<TInner, TComparisonItem>(
public class EnumerableOrderedByAssertCondition<TActual, TInner, TComparisonItem>(
IComparer<TComparisonItem?> comparer,
Func<TInner, TComparisonItem> comparisonItemSelector,
Order order)
: BaseAssertCondition<IEnumerable<TInner>>
: BaseAssertCondition<TActual> where TActual : IEnumerable<TInner>
{
protected override string GetExpectation()
{
return $"to be in {order} order";
}

protected override ValueTask<AssertionResult> GetResult(
IEnumerable<TInner>? actualValue, Exception? exception,
TActual? actualValue, Exception? exception,
AssertionMetadata assertionMetadata
)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public static partial class HasExtensions
{
public static SingleItemAssertionBuilderWrapper<IEnumerable<TInner>, TInner> HasSingleItem<TInner>(this IValueSource<IEnumerable<TInner>> valueSource)
{
var invokableValueAssertionBuilder = valueSource.RegisterAssertion(new EnumerableCountEqualToExpectedValueAssertCondition<TInner>(1), []);
var invokableValueAssertionBuilder = valueSource.RegisterAssertion(new EnumerableCountEqualToExpectedValueAssertCondition<IEnumerable<TInner>, TInner>(1), []);

return new SingleItemAssertionBuilderWrapper<IEnumerable<TInner>, TInner>(invokableValueAssertionBuilder);
}
Expand All @@ -23,12 +23,12 @@ public static InvokableValueAssertionBuilder<IEnumerable<TInner>> HasDistinctIte

public static InvokableValueAssertionBuilder<IEnumerable<TInner>> HasDistinctItems<TInner>(this IValueSource<IEnumerable<TInner>> valueSource, IEqualityComparer<TInner> equalityComparer)
{
return valueSource.RegisterAssertion(new EnumerableDistinctItemsExpectedValueAssertCondition<TInner>(equalityComparer), []);
return valueSource.RegisterAssertion(new EnumerableDistinctItemsExpectedValueAssertCondition<IEnumerable<TInner>, TInner>(equalityComparer), []);
}

public static EnumerableCount<TInner> HasCount<TInner>(this IValueSource<IEnumerable<TInner>> valueSource)
public static EnumerableCount<IEnumerable<TInner>, TInner> HasCount<TInner>(this IValueSource<IEnumerable<TInner>> valueSource)
{
valueSource.AppendExpression("HasCount()");
return new EnumerableCount<TInner>(valueSource);
return new EnumerableCount<IEnumerable<TInner>, TInner>(valueSource);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#nullable disable

using System.Collections.Immutable;
using TUnit.Assertions.AssertConditions.Collections;
using TUnit.Assertions.AssertConditions.Interfaces;
using TUnit.Assertions.AssertionBuilders;
using TUnit.Assertions.AssertionBuilders.Wrappers;

namespace TUnit.Assertions.Extensions;

public static partial class HasExtensions
{
public static SingleItemAssertionBuilderWrapper<ImmutableArray<TInner>, TInner> HasSingleItem<TInner>(this IValueSource<ImmutableArray<TInner>> valueSource)
{
var invokableValueAssertionBuilder = valueSource.RegisterAssertion(new EnumerableCountEqualToExpectedValueAssertCondition<ImmutableArray<TInner>, TInner>(1), []);

return new SingleItemAssertionBuilderWrapper<ImmutableArray<TInner>, TInner>(invokableValueAssertionBuilder);
}

public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> HasDistinctItems<TInner>(this IValueSource<ImmutableArray<TInner>> valueSource)
{
return HasDistinctItems(valueSource, EqualityComparer<TInner>.Default);
}

public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> HasDistinctItems<TInner>(this IValueSource<ImmutableArray<TInner>> valueSource, IEqualityComparer<TInner> equalityComparer)
{
return valueSource.RegisterAssertion(new EnumerableDistinctItemsExpectedValueAssertCondition<ImmutableArray<TInner>, TInner>(equalityComparer), []);
}

public static EnumerableCount<ImmutableArray<TInner>, TInner> HasCount<TInner>(this IValueSource<ImmutableArray<TInner>> valueSource)
{
valueSource.AppendExpression("HasCount()");
return new EnumerableCount<ImmutableArray<TInner>, TInner>(valueSource);
}
}
114 changes: 114 additions & 0 deletions TUnit.Assertions/Assertions/Collections/ImmutableArrayIsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#nullable disable

using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using TUnit.Assertions.AssertConditions.Collections;
using TUnit.Assertions.AssertConditions.Interfaces;
using TUnit.Assertions.AssertionBuilders;
using TUnit.Assertions.Enums;
using TUnit.Assertions.Equality;

namespace TUnit.Assertions.Extensions;

[SuppressMessage("Usage", "TUnitAssertions0003:Compiler argument populated")]
public static class ImmutableArrayIsExtensions
{
public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> IsEquivalentTo<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
TInner>(this IValueSource<ImmutableArray<TInner>> valueSource, ImmutableArray<TInner> expected, [CallerArgumentExpression(nameof(expected))] string doNotPopulateThisValue = null)
{
return IsEquivalentTo(valueSource, expected, new CollectionEquivalentToEqualityComparer<TInner>(), doNotPopulateThisValue);
}

public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> IsEquivalentTo<
TInner>(this IValueSource<ImmutableArray<TInner>> valueSource,
ImmutableArray<TInner> expected, IEqualityComparer<TInner> comparer,
[CallerArgumentExpression(nameof(expected))] string doNotPopulateThisValue = null)
{
return IsEquivalentTo(valueSource, expected, comparer, CollectionOrdering.Matching, doNotPopulateThisValue);
}

public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> IsEquivalentTo<
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.NonPublicFields | DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)]
TInner>(this IValueSource<ImmutableArray<TInner>> valueSource, ImmutableArray<TInner> expected, CollectionOrdering collectionOrdering, [CallerArgumentExpression(nameof(expected))] string doNotPopulateThisValue = null)
{
return IsEquivalentTo(valueSource, expected, new CollectionEquivalentToEqualityComparer<TInner>(), collectionOrdering, doNotPopulateThisValue);
}

public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> IsEquivalentTo<TInner>(this IValueSource<ImmutableArray<TInner>> valueSource, ImmutableArray<TInner> expected, IEqualityComparer<TInner> comparer, CollectionOrdering collectionOrdering, [CallerArgumentExpression(nameof(expected))] string doNotPopulateThisValue = null)
{
return valueSource.RegisterAssertion(
new EnumerableEquivalentToExpectedValueAssertCondition<ImmutableArray<TInner>, TInner>(expected,
comparer, collectionOrdering), [doNotPopulateThisValue]);
}

public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> IsInOrder<TInner>(
this IValueSource<ImmutableArray<TInner>> valueSource)
{
return IsOrderedBy(valueSource, x => x, Comparer<TInner>.Default, null);
}

public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> IsInDescendingOrder<TInner>(
this IValueSource<ImmutableArray<TInner>> valueSource)
{
return IsOrderedByDescending(valueSource, x => x, Comparer<TInner>.Default, null);
}

public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> IsInOrder<TInner>(
this IValueSource<ImmutableArray<TInner>> valueSource,
IComparer<TInner> comparer)
{
return IsOrderedBy(valueSource, x => x, comparer, null);
}

public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> IsInDescendingOrder<TInner>(
this IValueSource<ImmutableArray<TInner>> valueSource,
IComparer<TInner> comparer)
{
return IsOrderedByDescending(valueSource, x => x, comparer, null);
}

public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> IsOrderedBy<TInner, TComparisonItem>(
this IValueSource<ImmutableArray<TInner>> valueSource,
Func<TInner, TComparisonItem> comparisonItemSelector,
[CallerArgumentExpression(nameof(comparisonItemSelector))] string doNotPopulateThisValue = null)
{
return IsOrderedBy(valueSource, comparisonItemSelector, Comparer<TComparisonItem>.Default, doNotPopulateThisValue);
}

public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> IsOrderedByDescending<TInner, TComparisonItem>(
this IValueSource<ImmutableArray<TInner>> valueSource,
Func<TInner, TComparisonItem> comparisonItemSelector,
[CallerArgumentExpression(nameof(comparisonItemSelector))] string doNotPopulateThisValue = null)
{
return IsOrderedByDescending(valueSource, comparisonItemSelector, Comparer<TComparisonItem>.Default, doNotPopulateThisValue);
}

public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> IsOrderedBy<TInner, TComparisonItem>(
this IValueSource<ImmutableArray<TInner>> valueSource,
Func<TInner, TComparisonItem> comparisonItemSelector,
IComparer<TComparisonItem> comparer,
[CallerArgumentExpression(nameof(comparisonItemSelector))] string doNotPopulateThisValue = null,
[CallerArgumentExpression(nameof(comparer))] string doNotPopulateThisValue2 = null)
{
return valueSource.RegisterAssertion(
new EnumerableOrderedByAssertCondition<ImmutableArray<TInner>, TInner, TComparisonItem>(comparer, comparisonItemSelector, Order.Ascending), [doNotPopulateThisValue, doNotPopulateThisValue2]);
}

public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> IsOrderedByDescending<TInner, TComparisonItem>(
this IValueSource<ImmutableArray<TInner>> valueSource,
Func<TInner, TComparisonItem> comparisonItemSelector,
IComparer<TComparisonItem> comparer,
[CallerArgumentExpression(nameof(comparisonItemSelector))] string doNotPopulateThisValue = null,
[CallerArgumentExpression(nameof(comparer))] string doNotPopulateThisValue2 = null)
{
return valueSource.RegisterAssertion(
new EnumerableOrderedByAssertCondition<ImmutableArray<TInner>, TInner, TComparisonItem>(comparer, comparisonItemSelector, Order.Descending), [doNotPopulateThisValue, doNotPopulateThisValue2]);
}

public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> IsEmpty<TInner>(this IValueSource<ImmutableArray<TInner>> valueSource)
{
return valueSource.RegisterAssertion(new EnumerableCountEqualToExpectedValueAssertCondition<ImmutableArray<TInner>, TInner>(0), []);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#nullable disable

using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using TUnit.Assertions.AssertConditions.Collections;
using TUnit.Assertions.AssertConditions.Interfaces;
using TUnit.Assertions.AssertionBuilders;

namespace TUnit.Assertions.Extensions;

public static class ImmutableArrayIsNotExtensions
{
public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> IsNotEquivalentTo<TInner>(this IValueSource<ImmutableArray<TInner>> valueSource, IEnumerable<TInner> expected, IEqualityComparer<TInner> equalityComparer = null, [CallerArgumentExpression(nameof(expected))] string doNotPopulateThisValue = null)
{
return valueSource.RegisterAssertion(new EnumerableNotEquivalentToExpectedValueAssertCondition<ImmutableArray<TInner>, TInner>(expected, equalityComparer)
, [doNotPopulateThisValue]);
}

public static InvokableValueAssertionBuilder<ImmutableArray<TInner>> IsNotEmpty<TInner>(this IValueSource<ImmutableArray<TInner>> valueSource)
{
return valueSource.RegisterAssertion(new EnumerableCountNotEqualToExpectedValueAssertCondition<ImmutableArray<TInner>, TInner>(0)
, []);
}
}
Loading

0 comments on commit 2b6b558

Please sign in to comment.