Skip to content

.LastItem().Satisfies() loses specialised assertion source for item #5778

@thomhurst

Description

@thomhurst

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:250ListLastItemSource<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:

  1. 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.
  2. Add an internal CreateSatisfiesAssertion(Func<TItem, IAssertion?>, string?) helper alongside, mirroring ListItemAtSource.CreateSatisfiesAssertion.
  3. 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.
  4. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions