Mirror of #5706 for the LastItem path. Surfaced in code review of PR #5764.
Problem
ListLastItemSource<TList, TItem>.Satisfies(...) and ReadOnlyListLastItemSource<TList, TItem>.Satisfies(...) still pass IAssertionSource<TItem> (a generic fallback) into the lambda. When TItem is a string, collection, dictionary, or set, specialised assertion methods are unreachable — same shape as #5706.
Repro
IList<List<int>> nestedLists = new List<List<int>> { new() { 1, 2, 3 } };
// Fails: item is IAssertionSource<List<int>>, no .Count() in scope
await Assert.That(nestedLists).LastItem().Satisfies(item => item.Count().IsEqualTo(3));
// IList<string>
IList<string> strings = new List<string> { "alpha" };
await Assert.That(strings).LastItem().Satisfies(s => s.Contains("foo"));
Affected signatures
TUnit.Assertions/Conditions/ListAssertions.cs:250 — ListLastItemSource<TList, TItem>.Satisfies(Func<IAssertionSource<TItem>, Assertion<TItem>?>, ...)
TUnit.Assertions/Conditions/ReadOnlyListAssertions.cs (mirror) — ReadOnlyListLastItemSource<TList, TItem>.Satisfies(...)
Proposed fix
Apply the same IAssertionSourceFor<TItem, TSelf> pattern PR #5764 introduced for ItemAt:
- Add a generic
Satisfies<TSource>(Func<TSource, IAssertion?>, ...) instance method on ListLastItemSource and ReadOnlyListLastItemSource, constrained where TSource : IAssertionSourceFor<TItem, TSource>. Wrap in #if !NETSTANDARD2_0.
- Add an internal
CreateSatisfiesAssertion(Func<TItem, IAssertion?>, string?) helper alongside, mirroring ListItemAtSource.CreateSatisfiesAssertion.
- Add a parallel
ListLastItemSatisfiesExtensions (or extend the existing item-at extensions file) with the same per-shape thin delegators — interface-shaped overloads delegate to Satisfies<TSource>, concrete List<T>/Dictionary<K,V> overloads work via the same dual IAssertionSourceFor impls already on ListAssertion and MutableDictionaryAssertion.
- Mirror the
Issue5706Tests set against LastItem() to keep coverage parity.
Reference
PR #5764 — same pattern applied to ItemAt. Files to model after:
- Generic dispatch + internal helper:
TUnit.Assertions/Conditions/ListAssertions.cs (Satisfies on ListItemAtSource)
- Per-shape delegators:
TUnit.Assertions/Extensions/ListItemAtSatisfiesExtensions.cs
- Tests:
TUnit.Assertions.Tests/Bugs/Issue5706Tests.cs
Acceptance
- Specialised assertions reachable inside
.LastItem().Satisfies(...) lambda for string/collection/dict/set items.
- Same on the
ReadOnlyListLastItemSource path.
- Tests cover each specialised element shape (interface and concrete).
Mirror of #5706 for the LastItem path. Surfaced in code review of PR #5764.
Problem
ListLastItemSource<TList, TItem>.Satisfies(...)andReadOnlyListLastItemSource<TList, TItem>.Satisfies(...)still passIAssertionSource<TItem>(a generic fallback) into the lambda. WhenTItemis a string, collection, dictionary, or set, specialised assertion methods are unreachable — same shape as #5706.Repro
Affected signatures
TUnit.Assertions/Conditions/ListAssertions.cs:250—ListLastItemSource<TList, TItem>.Satisfies(Func<IAssertionSource<TItem>, Assertion<TItem>?>, ...)TUnit.Assertions/Conditions/ReadOnlyListAssertions.cs(mirror) —ReadOnlyListLastItemSource<TList, TItem>.Satisfies(...)Proposed fix
Apply the same
IAssertionSourceFor<TItem, TSelf>pattern PR #5764 introduced forItemAt:Satisfies<TSource>(Func<TSource, IAssertion?>, ...)instance method onListLastItemSourceandReadOnlyListLastItemSource, constrainedwhere TSource : IAssertionSourceFor<TItem, TSource>. Wrap in#if !NETSTANDARD2_0.CreateSatisfiesAssertion(Func<TItem, IAssertion?>, string?)helper alongside, mirroringListItemAtSource.CreateSatisfiesAssertion.ListLastItemSatisfiesExtensions(or extend the existing item-at extensions file) with the same per-shape thin delegators — interface-shaped overloads delegate toSatisfies<TSource>, concreteList<T>/Dictionary<K,V>overloads work via the same dualIAssertionSourceForimpls already onListAssertionandMutableDictionaryAssertion.Issue5706Testsset againstLastItem()to keep coverage parity.Reference
PR #5764 — same pattern applied to
ItemAt. Files to model after:TUnit.Assertions/Conditions/ListAssertions.cs(Satisfies onListItemAtSource)TUnit.Assertions/Extensions/ListItemAtSatisfiesExtensions.csTUnit.Assertions.Tests/Bugs/Issue5706Tests.csAcceptance
.LastItem().Satisfies(...)lambda for string/collection/dict/set items.ReadOnlyListLastItemSourcepath.