diff --git a/TUnit.Assertions.Tests/MemberCollectionAssertionTests.cs b/TUnit.Assertions.Tests/MemberCollectionAssertionTests.cs new file mode 100644 index 0000000000..f8e6234352 --- /dev/null +++ b/TUnit.Assertions.Tests/MemberCollectionAssertionTests.cs @@ -0,0 +1,372 @@ +namespace TUnit.Assertions.Tests; + +/// +/// Tests for Member assertions with collection types (arrays, lists, dictionaries). +/// Addresses issue #3495 where collection assertion methods were not available within Member lambdas. +/// +public class MemberCollectionAssertionTests +{ + [Test] + public async Task Member_Array_HasCount_Passes() + { + var obj = new TestClass + { + Tags = ["tag1", "tag2", "tag3"] + }; + + await Assert.That(obj) + .Member(x => x.Tags, tags => tags.HasCount(3)); + } + + [Test] + public async Task Member_Array_HasCount_Fails() + { + var obj = new TestClass + { + Tags = ["tag1", "tag2"] + }; + + var exception = await Assert.ThrowsAsync(async () => + await Assert.That(obj).Member(x => x.Tags, tags => tags.HasCount(5))); + + await Assert.That(exception.Message).Contains("to have count 5"); + await Assert.That(exception.Message).Contains("but found 2"); + } + + [Test] + public async Task Member_Array_Contains_Passes() + { + var obj = new TestClass + { + Tags = ["tag1", "tag2", "tag3"] + }; + + await Assert.That(obj) + .Member(x => x.Tags, tags => tags.Contains("tag2")); + } + + [Test] + public async Task Member_Array_Contains_Fails() + { + var obj = new TestClass + { + Tags = ["tag1", "tag2"] + }; + + var exception = await Assert.ThrowsAsync(async () => + await Assert.That(obj).Member(x => x.Tags, tags => tags.Contains("missing"))); + + await Assert.That(exception.Message).Contains("to contain"); + await Assert.That(exception.Message).Contains("missing"); + } + + [Test] + public async Task Member_Array_IsEmpty_Passes() + { + var obj = new TestClass + { + Tags = [] + }; + + await Assert.That(obj) + .Member(x => x.Tags, tags => tags.IsEmpty()); + } + + [Test] + public async Task Member_Array_IsEmpty_Fails() + { + var obj = new TestClass + { + Tags = ["tag1"] + }; + + var exception = await Assert.ThrowsAsync(async () => + await Assert.That(obj).Member(x => x.Tags, tags => tags.IsEmpty())); + + await Assert.That(exception.Message).Contains("to be empty"); + } + + [Test] + public async Task Member_Array_IsNotEmpty_Passes() + { + var obj = new TestClass + { + Tags = ["tag1"] + }; + + await Assert.That(obj) + .Member(x => x.Tags, tags => tags.IsNotEmpty()); + } + + [Test] + public async Task Member_Array_Chained_Assertions_Passes() + { + var obj = new TestClass + { + Tags = ["tag1", "tag2"] + }; + + await Assert.That(obj) + .Member(x => x.Tags, tags => tags.HasCount(2).And.Contains("tag1").And.Contains("tag2")); + } + + [Test] + public async Task Member_Array_Chained_With_Parent_IsNotNull() + { + var obj = new TestClass + { + Tags = ["tag1"] + }; + + await Assert.That(obj) + .IsNotNull() + .And.Member(x => x.Tags, tags => tags.HasCount(1).And.Contains("tag1")); + } + + [Test] + public async Task Member_List_HasCount_Passes() + { + var obj = new TestClass + { + Items = new List { 1, 2, 3 } + }; + + await Assert.That(obj) + .Member(x => x.Items, items => items.HasCount(3)); + } + + [Test] + public async Task Member_List_Contains_Passes() + { + var obj = new TestClass + { + Items = new List { 1, 2, 3 } + }; + + await Assert.That(obj) + .Member(x => x.Items, items => items.Contains(2)); + } + + [Test] + public async Task Member_List_All_Predicate_Passes() + { + var obj = new TestClass + { + Items = new List { 2, 4, 6 } + }; + + await Assert.That(obj) + .Member(x => x.Items, items => items.All(x => x % 2 == 0)); + } + + [Test] + public async Task Member_List_Any_Predicate_Passes() + { + var obj = new TestClass + { + Items = new List { 1, 2, 3 } + }; + + await Assert.That(obj) + .Member(x => x.Items, items => items.Any(x => x > 2)); + } + + [Test] + public async Task Member_Dictionary_IsEmpty_Passes() + { + var obj = new TestClass + { + Attributes = new Dictionary() + }; + + await Assert.That(obj) + .Member(x => x.Attributes, attrs => attrs.IsEmpty()); + } + + [Test] + public async Task Member_Dictionary_IsEmpty_Fails() + { + var obj = new TestClass + { + Attributes = new Dictionary { ["key"] = "value" } + }; + + var exception = await Assert.ThrowsAsync(async () => + await Assert.That(obj).Member(x => x.Attributes, attrs => attrs.IsEmpty())); + + await Assert.That(exception.Message).Contains("to be empty"); + } + + [Test] + public async Task Member_Dictionary_HasCount_Passes() + { + var obj = new TestClass + { + Attributes = new Dictionary { ["key1"] = "value1", ["key2"] = "value2" } + }; + + await Assert.That(obj) + .Member(x => x.Attributes, attrs => attrs.HasCount(2)); + } + + [Test] + public async Task Member_Dictionary_ContainsKey_Passes() + { + var obj = new TestClass + { + Attributes = new Dictionary { ["status"] = "active", ["priority"] = "high" } + }; + + await Assert.That(obj) + .Member(x => x.Attributes, attrs => attrs.ContainsKey("status")); + } + + [Test] + public async Task Member_Dictionary_ContainsKey_And_IsNotEmpty() + { + var obj = new TestClass + { + Attributes = new Dictionary { ["status"] = "active" } + }; + + await Assert.That(obj) + .Member(x => x.Attributes, attrs => attrs.ContainsKey("status").And.IsNotEmpty()); + } + + [Test] + public async Task Member_Dictionary_DoesNotContainKey_Passes() + { + var obj = new TestClass + { + Attributes = new Dictionary { ["key1"] = "value1" } + }; + + await Assert.That(obj) + .Member(x => x.Attributes, attrs => attrs.DoesNotContainKey("missing")); + } + + [Test] + public async Task Member_Enumerable_IsInOrder_Passes() + { + var obj = new TestClass + { + Items = new List { 1, 2, 3, 4, 5 } + }; + + await Assert.That(obj) + .Member(x => x.Items, items => items.IsInOrder()); + } + + [Test] + public async Task Member_Complex_Chain_Multiple_Collections() + { + var obj = new TestClass + { + Tags = ["important", "urgent"], + Items = new List { 1, 2, 3 }, + Attributes = new Dictionary { ["status"] = "active" } + }; + + await Assert.That(obj) + .IsNotNull() + .And.Member(x => x.Tags, tags => tags.HasCount(2).And.Contains("important")) + .And.Member(x => x.Items, items => items.HasCount(3).And.All(x => x > 0)) + .And.Member(x => x.Attributes, attrs => attrs.HasCount(1)); + } + + [Test] + public async Task Member_Array_Contains_Predicate_Passes() + { + var obj = new TestClass + { + Tags = ["test1", "test2", "other"] + }; + + await Assert.That(obj) + .Member(x => x.Tags, tags => tags.Contains(t => t.StartsWith("test"))); + } + + [Test] + public async Task Member_Array_DoesNotContain_Passes() + { + var obj = new TestClass + { + Tags = ["tag1", "tag2"] + }; + + await Assert.That(obj) + .Member(x => x.Tags, tags => tags.DoesNotContain("missing")); + } + + [Test] + public async Task Member_Array_HasSingleItem_Passes() + { + var obj = new TestClass + { + Tags = ["only"] + }; + + await Assert.That(obj) + .Member(x => x.Tags, tags => tags.HasSingleItem()); + } + + [Test] + public async Task Member_IEnumerable_HasCount_Passes() + { + var obj = new TestClass + { + Sequence = Enumerable.Range(1, 5) + }; + + await Assert.That(obj) + .Member(x => x.Sequence, seq => seq.HasCount(5)); + } + + [Test] + public async Task Issue3495_ReportedCase_Array_HasCount_And_Contains() + { + // This is the exact scenario reported in issue #3495 + var obj = new TestClass + { + Tags = ["pile", "other"] + }; + + await Assert.That(obj) + .IsNotNull() + .And.Member(x => x.Tags, tags => tags.HasCount(2).And.Contains("pile")); + } + + [Test] + public async Task Issue3495_ReportedCase_Dictionary_IsEmpty() + { + // This is the dictionary scenario mentioned in issue #3495 + var obj = new TestClass + { + Attributes = new Dictionary() + }; + + await Assert.That(obj) + .Member(x => x.Attributes, attrs => attrs.IsEmpty()); + } + + [Test] + public async Task Issue3495_Dictionary_ContainsKey() + { + // Testing dictionary-specific methods like ContainsKey + var obj = new TestClass + { + Attributes = new Dictionary { ["status"] = "active" } + }; + + await Assert.That(obj) + .Member(x => x.Attributes, attrs => attrs.ContainsKey("status").And.HasCount(1)); + } + + private class TestClass + { + public string[] Tags { get; init; } = []; + public List Items { get; init; } = []; + public Dictionary Attributes { get; init; } = new(); + public IEnumerable Sequence { get; init; } = Enumerable.Empty(); + } +} diff --git a/TUnit.Assertions/Conditions/CollectionAssertions.cs b/TUnit.Assertions/Conditions/CollectionAssertions.cs index 4a640ca8a4..bf9973f7c4 100644 --- a/TUnit.Assertions/Conditions/CollectionAssertions.cs +++ b/TUnit.Assertions/Conditions/CollectionAssertions.cs @@ -336,7 +336,7 @@ protected override Task CheckAsync(EvaluationMetadata $"to have count {_expectedCount}"; diff --git a/TUnit.Assertions/Conditions/EqualsAssertion.cs b/TUnit.Assertions/Conditions/EqualsAssertion.cs index 8c845236d2..821a2b4e7f 100644 --- a/TUnit.Assertions/Conditions/EqualsAssertion.cs +++ b/TUnit.Assertions/Conditions/EqualsAssertion.cs @@ -212,7 +212,7 @@ private static (bool IsSuccess, string? Message) DeepEquals(object? actual, obje return (true, null); } - protected override string GetExpectation() => $"to be equal to {_expected}"; + protected override string GetExpectation() => $"to be equal to {(_expected is string s ? $"\"{s}\"" : _expected)}"; /// /// Comparer that uses reference equality instead of value equality. diff --git a/TUnit.Assertions/Conditions/MemberAssertion.cs b/TUnit.Assertions/Conditions/MemberAssertion.cs index d3213ad753..78969efaa1 100644 --- a/TUnit.Assertions/Conditions/MemberAssertion.cs +++ b/TUnit.Assertions/Conditions/MemberAssertion.cs @@ -143,6 +143,32 @@ public AssertionSourceAdapter(AssertionContext context) } } +/// +/// Specialized adapter for collection member assertions. +/// Implements IAssertionSource<TCollection> while providing collection assertion methods from CollectionAssertionBase. +/// +public class CollectionMemberAssertionAdapter : Sources.CollectionAssertionBase + where TCollection : IEnumerable +{ + internal CollectionMemberAssertionAdapter(AssertionContext context) + : base(context) + { + } +} + +/// +/// Specialized adapter for dictionary member assertions. +/// Implements IAssertionSource<TDictionary> while providing dictionary assertion methods from DictionaryAssertionBase. +/// +public class DictionaryMemberAssertionAdapter : Sources.DictionaryAssertionBase + where TDictionary : IReadOnlyDictionary +{ + internal DictionaryMemberAssertionAdapter(AssertionContext context) + : base(context) + { + } +} + /// /// Combines a pending assertion with a member assertion using AND logic. /// Both assertions must pass for the overall assertion to succeed. diff --git a/TUnit.Assertions/Extensions/AssertionExtensions.cs b/TUnit.Assertions/Extensions/AssertionExtensions.cs index e6fbe3d50d..55d966c3d5 100644 --- a/TUnit.Assertions/Extensions/AssertionExtensions.cs +++ b/TUnit.Assertions/Extensions/AssertionExtensions.cs @@ -177,6 +177,216 @@ public static IsTypeOfRuntimeAssertion IsOfType( return new IsTypeOfRuntimeAssertion(source.Context, expectedType); } + /// + /// Asserts on a dictionary member of an object using a lambda selector and assertion lambda. + /// The assertion lambda receives dictionary assertion methods (ContainsKey, ContainsValue, IsEmpty, etc.). + /// After the member assertion completes, returns to the parent object context for further chaining. + /// Example: await Assert.That(myObject).Member(x => x.Attributes, attrs => attrs.ContainsKey("status").And.IsNotEmpty()); + /// + [OverloadResolutionPriority(2)] + public static MemberAssertionResult Member( + this IAssertionSource source, + Expression>> memberSelector, + Func, TKey, TValue>, Assertion>> assertions) + { + var parentContext = source.Context; + var memberPath = GetMemberPath(memberSelector); + + parentContext.ExpressionBuilder.Append($".Member(x => x.{memberPath}, ...)"); + + // Check if there's a pending link (from .And or .Or) that needs to be consumed + var (pendingAssertion, combinerType) = parentContext.ConsumePendingLink(); + + // Map to member context + var memberContext = parentContext.Map>(obj => + { + if (obj == null) + { + throw new InvalidOperationException($"Object `{typeof(TObject).Name}` was null"); + } + + var compiled = memberSelector.Compile(); + return compiled(obj); + }); + + // Create a DictionaryMemberAssertionAdapter for the member + var dictionaryAdapter = new DictionaryMemberAssertionAdapter, TKey, TValue>(memberContext); + var memberAssertion = assertions(dictionaryAdapter); + + // Type-erase to object? for storage + var erasedAssertion = new TypeErasedAssertion>(memberAssertion); + + // If there was a pending link, wrap both assertions together + if (pendingAssertion != null && combinerType != null) + { + // Create a combined wrapper that executes the pending assertion first (or together for Or) + Assertion combinedAssertion = combinerType == CombinerType.And + ? new CombinedAndAssertion(parentContext, pendingAssertion, erasedAssertion) + : new CombinedOrAssertion(parentContext, pendingAssertion, erasedAssertion); + + return new MemberAssertionResult(parentContext, combinedAssertion); + } + + return new MemberAssertionResult(parentContext, erasedAssertion); + } + + /// + /// Asserts on a dictionary member of an object using a lambda selector and assertion lambda. + /// The assertion lambda receives dictionary assertion methods (ContainsKey, ContainsValue, IsEmpty, etc.). + /// After the member assertion completes, returns to the parent object context for further chaining. + /// Example: await Assert.That(myObject).Member(x => x.Attributes, attrs => attrs.ContainsKey("status").And.IsNotEmpty()); + /// + [OverloadResolutionPriority(2)] + public static MemberAssertionResult Member( + this IAssertionSource source, + Expression>> memberSelector, + Func, TKey, TValue>, object> assertions) + { + var parentContext = source.Context; + var memberPath = GetMemberPath(memberSelector); + + parentContext.ExpressionBuilder.Append($".Member(x => x.{memberPath}, ...)"); + + // Check if there's a pending link (from .And or .Or) that needs to be consumed + var (pendingAssertion, combinerType) = parentContext.ConsumePendingLink(); + + // Map to member context + var memberContext = parentContext.Map>(obj => + { + if (obj == null) + { + throw new InvalidOperationException($"Object `{typeof(TObject).Name}` was null"); + } + + var compiled = memberSelector.Compile(); + return compiled(obj); + }); + + // Create a DictionaryMemberAssertionAdapter for the member + var dictionaryAdapter = new DictionaryMemberAssertionAdapter, TKey, TValue>(memberContext); + var memberAssertionObj = assertions(dictionaryAdapter); + + // Type-erase to object? for storage + var erasedAssertion = WrapMemberAssertion>(memberAssertionObj); + + // If there was a pending link, wrap both assertions together + if (pendingAssertion != null && combinerType != null) + { + Assertion combinedAssertion = combinerType == CombinerType.And + ? new CombinedAndAssertion(parentContext, pendingAssertion, erasedAssertion) + : new CombinedOrAssertion(parentContext, pendingAssertion, erasedAssertion); + + return new MemberAssertionResult(parentContext, combinedAssertion); + } + + return new MemberAssertionResult(parentContext, erasedAssertion); + } + + /// + /// Asserts on a collection member of an object using a lambda selector and assertion lambda. + /// The assertion lambda receives collection assertion methods (HasCount, Contains, IsEmpty, etc.). + /// After the member assertion completes, returns to the parent object context for further chaining. + /// Example: await Assert.That(myObject).Member(x => x.Tags, tags => tags.HasCount(1).And.Contains("value")); + /// + [OverloadResolutionPriority(1)] + public static MemberAssertionResult Member( + this IAssertionSource source, + Expression>> memberSelector, + Func, TItem>, Assertion>> assertions) + { + var parentContext = source.Context; + var memberPath = GetMemberPath(memberSelector); + + parentContext.ExpressionBuilder.Append($".Member(x => x.{memberPath}, ...)"); + + // Check if there's a pending link (from .And or .Or) that needs to be consumed + var (pendingAssertion, combinerType) = parentContext.ConsumePendingLink(); + + // Map to member context + var memberContext = parentContext.Map>(obj => + { + if (obj == null) + { + throw new InvalidOperationException($"Object `{typeof(TObject).Name}` was null"); + } + + var compiled = memberSelector.Compile(); + return compiled(obj); + }); + + // Create a CollectionMemberAssertionAdapter for the member + var collectionAdapter = new CollectionMemberAssertionAdapter, TItem>(memberContext); + var memberAssertion = assertions(collectionAdapter); + + // Type-erase to object? for storage + var erasedAssertion = new TypeErasedAssertion>(memberAssertion); + + // If there was a pending link, wrap both assertions together + if (pendingAssertion != null && combinerType != null) + { + // Create a combined wrapper that executes the pending assertion first (or together for Or) + Assertion combinedAssertion = combinerType == CombinerType.And + ? new CombinedAndAssertion(parentContext, pendingAssertion, erasedAssertion) + : new CombinedOrAssertion(parentContext, pendingAssertion, erasedAssertion); + + return new MemberAssertionResult(parentContext, combinedAssertion); + } + + return new MemberAssertionResult(parentContext, erasedAssertion); + } + + /// + /// Asserts on a collection member of an object using a lambda selector and assertion lambda. + /// The assertion lambda receives collection assertion methods (HasCount, Contains, IsEmpty, etc.). + /// After the member assertion completes, returns to the parent object context for further chaining. + /// Example: await Assert.That(myObject).Member(x => x.Tags, tags => tags.HasCount(1).And.Contains("value")); + /// + [OverloadResolutionPriority(1)] + public static MemberAssertionResult Member( + this IAssertionSource source, + Expression>> memberSelector, + Func, TItem>, object> assertions) + { + var parentContext = source.Context; + var memberPath = GetMemberPath(memberSelector); + + parentContext.ExpressionBuilder.Append($".Member(x => x.{memberPath}, ...)"); + + // Check if there's a pending link (from .And or .Or) that needs to be consumed + var (pendingAssertion, combinerType) = parentContext.ConsumePendingLink(); + + // Map to member context + var memberContext = parentContext.Map>(obj => + { + if (obj == null) + { + throw new InvalidOperationException($"Object `{typeof(TObject).Name}` was null"); + } + + var compiled = memberSelector.Compile(); + return compiled(obj); + }); + + // Create a CollectionMemberAssertionAdapter for the member + var collectionAdapter = new CollectionMemberAssertionAdapter, TItem>(memberContext); + var memberAssertionObj = assertions(collectionAdapter); + + // Type-erase to object? for storage + var erasedAssertion = WrapMemberAssertion>(memberAssertionObj); + + // If there was a pending link, wrap both assertions together + if (pendingAssertion != null && combinerType != null) + { + Assertion combinedAssertion = combinerType == CombinerType.And + ? new CombinedAndAssertion(parentContext, pendingAssertion, erasedAssertion) + : new CombinedOrAssertion(parentContext, pendingAssertion, erasedAssertion); + + return new MemberAssertionResult(parentContext, combinedAssertion); + } + + return new MemberAssertionResult(parentContext, erasedAssertion); + } + /// /// Asserts on a member of an object using a lambda selector and assertion lambda. /// The assertion lambda receives the member value and can perform any assertions on it. @@ -209,7 +419,6 @@ public static MemberAssertionResult Member( }); // Let user build assertion via lambda - // Create a simple adapter that implements IAssertionSource var memberSource = new AssertionSourceAdapter(memberContext); var memberAssertion = assertions(memberSource); @@ -234,7 +443,7 @@ public static MemberAssertionResult Member( /// Asserts on a member of an object using a lambda selector and assertion lambda. /// The assertion lambda receives the member value and can perform any assertions on it. /// After the member assertion completes, returns to the parent object context for further chaining. - /// Example: await Assert.That(myObject).Member(x => x.Tags, tags => tags.Contains("value")); + /// Example: await Assert.That(myObject).Member(x => x.PropertyName, value => value.IsEqualTo(expectedValue)); /// public static MemberAssertionResult Member( this IAssertionSource source, diff --git a/TUnit.Assertions/Sources/CollectionAssertion.cs b/TUnit.Assertions/Sources/CollectionAssertion.cs index 25b1861d19..9d91a042fe 100644 --- a/TUnit.Assertions/Sources/CollectionAssertion.cs +++ b/TUnit.Assertions/Sources/CollectionAssertion.cs @@ -17,6 +17,11 @@ public CollectionAssertion(IEnumerable value, string? expression) { } + internal CollectionAssertion(AssertionContext> context) + : base(context) + { + } + private static StringBuilder CreateExpressionBuilder(string? expression) { var builder = new StringBuilder(); diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt index df8d331c86..47b1f92e33 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet10_0.verified.txt @@ -504,6 +504,8 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class CollectionMemberAssertionAdapter : . + where TCollection : . { } public class CollectionNotNullAssertion : . where TCollection : . { @@ -692,6 +694,8 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class DictionaryMemberAssertionAdapter : . + where TDictionary : . { } [.("HasFiles")] public class DirectoryHasFilesAssertion : .<.DirectoryInfo> { @@ -1880,8 +1884,16 @@ namespace .Extensions public static . IsPositive(this . source) where TValue : struct, { } public static . IsTypeOf(this . source) { } + [.(1)] + public static . Member(this . source, .<>> memberSelector, <.<., TItem>, .<.>> assertions) { } + [.(1)] + public static . Member(this . source, .<>> memberSelector, <.<., TItem>, object> assertions) { } public static . Member(this . source, .<> memberSelector, <., .> assertions) { } public static . Member(this . source, .<> memberSelector, <., object> assertions) { } + [.(2)] + public static . Member(this . source, .<>> memberSelector, <.<., TKey, TValue>, .<.>> assertions) { } + [.(2)] + public static . Member(this . source, .<>> memberSelector, <.<., TKey, TValue>, object> assertions) { } public static . Satisfies(this . source, predicate, [.("predicate")] string? expression = null) { } public static . Satisfies(this . source, > selector, <., .?> assertions, [.("selector")] string? selectorExpression = null) { } public static . Satisfies(this . source, selector, <., .?> assertions, [.("selector")] string? selectorExpression = null) { } diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt index 8b00b719b5..bf8a828a53 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet8_0.verified.txt @@ -501,6 +501,8 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class CollectionMemberAssertionAdapter : . + where TCollection : . { } public class CollectionNotNullAssertion : . where TCollection : . { @@ -689,6 +691,8 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class DictionaryMemberAssertionAdapter : . + where TDictionary : . { } [.("HasFiles")] public class DirectoryHasFilesAssertion : .<.DirectoryInfo> { @@ -1877,8 +1881,12 @@ namespace .Extensions public static . IsPositive(this . source) where TValue : struct, { } public static . IsTypeOf(this . source) { } + public static . Member(this . source, .<>> memberSelector, <.<., TItem>, .<.>> assertions) { } + public static . Member(this . source, .<>> memberSelector, <.<., TItem>, object> assertions) { } public static . Member(this . source, .<> memberSelector, <., .> assertions) { } public static . Member(this . source, .<> memberSelector, <., object> assertions) { } + public static . Member(this . source, .<>> memberSelector, <.<., TKey, TValue>, .<.>> assertions) { } + public static . Member(this . source, .<>> memberSelector, <.<., TKey, TValue>, object> assertions) { } public static . Satisfies(this . source, predicate, [.("predicate")] string? expression = null) { } public static . Satisfies(this . source, > selector, <., .?> assertions, [.("selector")] string? selectorExpression = null) { } public static . Satisfies(this . source, selector, <., .?> assertions, [.("selector")] string? selectorExpression = null) { } diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt index 4fe37c4155..d2801146cb 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -504,6 +504,8 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class CollectionMemberAssertionAdapter : . + where TCollection : . { } public class CollectionNotNullAssertion : . where TCollection : . { @@ -692,6 +694,8 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class DictionaryMemberAssertionAdapter : . + where TDictionary : . { } [.("HasFiles")] public class DirectoryHasFilesAssertion : .<.DirectoryInfo> { @@ -1880,8 +1884,16 @@ namespace .Extensions public static . IsPositive(this . source) where TValue : struct, { } public static . IsTypeOf(this . source) { } + [.(1)] + public static . Member(this . source, .<>> memberSelector, <.<., TItem>, .<.>> assertions) { } + [.(1)] + public static . Member(this . source, .<>> memberSelector, <.<., TItem>, object> assertions) { } public static . Member(this . source, .<> memberSelector, <., .> assertions) { } public static . Member(this . source, .<> memberSelector, <., object> assertions) { } + [.(2)] + public static . Member(this . source, .<>> memberSelector, <.<., TKey, TValue>, .<.>> assertions) { } + [.(2)] + public static . Member(this . source, .<>> memberSelector, <.<., TKey, TValue>, object> assertions) { } public static . Satisfies(this . source, predicate, [.("predicate")] string? expression = null) { } public static . Satisfies(this . source, > selector, <., .?> assertions, [.("selector")] string? selectorExpression = null) { } public static . Satisfies(this . source, selector, <., .?> assertions, [.("selector")] string? selectorExpression = null) { } diff --git a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt index 29795574b5..c7f4c66c88 100644 --- a/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Assertions_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -499,6 +499,8 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class CollectionMemberAssertionAdapter : . + where TCollection : . { } public class CollectionNotNullAssertion : . where TCollection : . { @@ -652,6 +654,8 @@ namespace .Conditions protected override .<.> CheckAsync(. metadata) { } protected override string GetExpectation() { } } + public class DictionaryMemberAssertionAdapter : . + where TDictionary : . { } [.("HasFiles")] public class DirectoryHasFilesAssertion : .<.DirectoryInfo> { @@ -1759,8 +1763,12 @@ namespace .Extensions public static . IsPositive(this . source) where TValue : struct, { } public static . IsTypeOf(this . source) { } + public static . Member(this . source, .<>> memberSelector, <.<., TItem>, .<.>> assertions) { } + public static . Member(this . source, .<>> memberSelector, <.<., TItem>, object> assertions) { } public static . Member(this . source, .<> memberSelector, <., .> assertions) { } public static . Member(this . source, .<> memberSelector, <., object> assertions) { } + public static . Member(this . source, .<>> memberSelector, <.<., TKey, TValue>, .<.>> assertions) { } + public static . Member(this . source, .<>> memberSelector, <.<., TKey, TValue>, object> assertions) { } public static . Satisfies(this . source, predicate, [.("predicate")] string? expression = null) { } public static . Satisfies(this . source, > selector, <., .?> assertions, [.("selector")] string? selectorExpression = null) { } public static . Satisfies(this . source, selector, <., .?> assertions, [.("selector")] string? selectorExpression = null) { }