Repository navigation
fix: Should().HaveSingleItem() returns the single item #7035
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,6 +44,7 @@ public sealed class ShouldExtensionGenerator : IIncrementalGenerator | |
| private const string DynamicallyAccessedMembersAttributeName = "DynamicallyAccessedMembersAttribute"; | ||
| private const string ShouldGeneratePartialAttributeFullName = "TUnit.Assertions.Should.Attributes.ShouldGeneratePartialAttribute"; | ||
| private const string ShouldExtensionsTypeFullName = "TUnit.Assertions.Should.ShouldExtensions"; | ||
| private const string HasSingleItemAssertionTypeName = "TUnit.Assertions.Conditions.HasSingleItemAssertion"; | ||
|
|
||
| private static readonly SymbolDisplayFormat NoGlobalFormat = | ||
| SymbolDisplayFormat.FullyQualifiedFormat | ||
|
|
@@ -1362,7 +1363,7 @@ private static void EmitWrapperPartial(SourceProductionContext ctx, WrapperData | |
| private static void EmitWrapperMethod(StringBuilder sb, WrapperData wrapper, WrapperMethodData m) | ||
| { | ||
| var positiveName = NameConjugator.Conjugate(m.SourceMethodName); | ||
| var returnType = $"global::TUnit.Assertions.Should.Core.ShouldAssertion<{wrapper.AssertionTypeArgDisplay}>"; | ||
| var returnType = GetShouldReturnType(m.ReturnTypeFullName, wrapper.AssertionTypeArgDisplay, m.ReturnTypeGenericArgs); | ||
|
|
||
| sb.AppendLine(); | ||
| if (!string.IsNullOrEmpty(m.RequiresUnreferencedCodeMessage)) | ||
|
|
@@ -1424,7 +1425,7 @@ private static void EmitWrapperMethod(StringBuilder sb, WrapperData wrapper, Wra | |
| sb.AppendLine(" {"); | ||
| sb.AppendLine(" inner.Because(__tunit_should_because);"); | ||
| sb.AppendLine(" }"); | ||
| sb.AppendLine($" return new global::TUnit.Assertions.Should.Core.ShouldAssertion<{wrapper.AssertionTypeArgDisplay}>(Context, inner);"); | ||
| sb.AppendLine($" return new {returnType}(Context, inner);"); | ||
| sb.AppendLine(" }"); | ||
| } | ||
|
|
||
|
|
@@ -1479,7 +1480,7 @@ p.DynamicallyAccessedMembersAttribute is null | |
| .Where(c => c is not null)); | ||
|
|
||
| var sourceType = $"global::TUnit.Assertions.Should.Core.IShouldSource<{m.SourceTypeArgDisplay}>"; | ||
| var returnType = $"global::TUnit.Assertions.Should.Core.ShouldAssertion<{m.AssertionTypeArgDisplay}>"; | ||
| var returnType = GetShouldReturnType(m.ReturnTypeFullName, m.AssertionTypeArgDisplay, m.ReturnTypeGenericArgs); | ||
|
|
||
| sb.AppendLine(); | ||
| if (!string.IsNullOrEmpty(m.RequiresUnreferencedCodeMessage)) | ||
|
|
@@ -1553,13 +1554,32 @@ p.DynamicallyAccessedMembersAttribute is null | |
| sb.AppendLine(" {"); | ||
| sb.AppendLine(" inner.Because(__tunit_should_because);"); | ||
| sb.AppendLine(" }"); | ||
| sb.AppendLine($" return new global::TUnit.Assertions.Should.Core.ShouldAssertion<{m.AssertionTypeArgDisplay}>(innerContext, inner);"); | ||
| sb.AppendLine($" return new {returnType}(innerContext, inner);"); | ||
| sb.AppendLine(" }"); | ||
| } | ||
|
|
||
| private static string FormatGenericArgs(EquatableArray<string> args) | ||
| => args.Length == 0 ? string.Empty : "<" + string.Join(", ", args) + ">"; | ||
|
|
||
| /// <summary> | ||
| /// <c>HasSingleItemAssertion</c> hides <c>GetAwaiter()</c> and returns the item. | ||
| /// <c>ShouldAssertion<T></c> would bind the base awaiter and return the collection, | ||
| /// so that one return type is wrapped in <c>ShouldSingleItemAssertion</c>. Predicate and | ||
| /// dictionary overloads keep <c>ShouldAssertion<T></c> because their return type name differs. | ||
| /// </summary> | ||
| private static string GetShouldReturnType( | ||
| string returnTypeFullName, | ||
| string assertionTypeArgDisplay, | ||
| EquatableArray<string> returnTypeGenericArgs) | ||
| { | ||
| if (returnTypeFullName == HasSingleItemAssertionTypeName) | ||
| { | ||
| return "global::TUnit.Assertions.Should.Core.ShouldSingleItemAssertion" + FormatGenericArgs(returnTypeGenericArgs); | ||
|
Comment on lines
+1575
to
+1577
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
AGENTS.md reference: AGENTS.md:L16-L16 Useful? React with 👍 / 👎. |
||
| } | ||
|
|
||
| return $"global::TUnit.Assertions.Should.Core.ShouldAssertion<{assertionTypeArgDisplay}>"; | ||
| } | ||
|
|
||
| private static MethodData[] DeduplicateMethods(MethodData[] methods) | ||
| { | ||
| var seen = new HashSet<string>(StringComparer.Ordinal); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| using System.Collections.Generic; | ||
| using System.ComponentModel; | ||
| using System.Runtime.CompilerServices; | ||
| using TUnit.Assertions.Conditions; | ||
| using TUnit.Assertions.Core; | ||
|
|
||
| namespace TUnit.Assertions.Should.Core; | ||
|
|
||
| /// <summary> | ||
| /// Per-call result of parameterless <c>HaveSingleItem()</c>. Wraps | ||
| /// <see cref="HasSingleItemAssertion{TCollection, TItem}"/> and forwards <c>await</c> to the | ||
| /// awaiter that type hides, so the awaited value is <typeparamref name="TItem"/>. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Class rather than <c>readonly struct</c>, for the same reason as <see cref="ShouldAssertion{T}"/>: | ||
| /// <see cref="GetAwaiter"/> forwards to a state-machine awaiter that mutates the wrapped assertion. | ||
| /// <see cref="And"/> and <see cref="Or"/> are read through <see cref="Assertion{TValue}"/> so the | ||
| /// <c>new</c> collection continuations on <see cref="HasSingleItemAssertion{TCollection, TItem}"/> | ||
| /// are not required; generated extensions continue from <see cref="ShouldContinuation{T}"/>. | ||
| /// </remarks> | ||
| public sealed class ShouldSingleItemAssertion<TCollection, TItem> : IShouldSource<TCollection>, IAssertion | ||
| where TCollection : IEnumerable<TItem> | ||
| { | ||
| private readonly HasSingleItemAssertion<TCollection, TItem> _inner; | ||
|
|
||
| public AssertionContext<TCollection> Context { get; } | ||
|
|
||
| [EditorBrowsable(EditorBrowsableState.Never)] | ||
| public ShouldSingleItemAssertion(AssertionContext<TCollection> context, HasSingleItemAssertion<TCollection, TItem> inner) | ||
| { | ||
| Context = context; | ||
| _inner = inner; | ||
| } | ||
|
|
||
| public TaskAwaiter<TItem> GetAwaiter() => _inner.GetAwaiter(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift Do not return an item when For 🤖 Prompt for AI AgentsThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
|
|
||
| public Task<TCollection?> AssertAsync() => _inner.AssertAsync(); | ||
|
|
||
| Task IAssertion.AssertAsync() => _inner.AssertAsync(); | ||
|
|
||
| public ShouldSingleItemAssertion<TCollection, TItem> Because(string message) | ||
| { | ||
| _inner.Because(message); | ||
| return this; | ||
| } | ||
|
|
||
| public ShouldContinuation<TCollection> And => new(CollectionAssertion.And.Context); | ||
|
|
||
| public ShouldContinuation<TCollection> Or => new(CollectionAssertion.Or.Context); | ||
|
|
||
| string? IShouldSource<TCollection>.ConsumeBecauseMessage() | ||
| => null; | ||
|
|
||
| private Assertion<TCollection> CollectionAssertion => _inner; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| using System.Collections.Generic; | ||
| using TUnit.Assertions.Should.Core; | ||
|
|
||
| namespace TUnit.Assertions.Should.Extensions; | ||
|
|
||
| public static partial class ShouldHasSingleItemAssertionExtensions | ||
| { | ||
| /// <summary> | ||
| /// Lets <c>And</c> and <c>Or</c> continuations on <see cref="IEnumerable{T}"/> call | ||
| /// <c>HaveSingleItem()</c> without type arguments. The generated two-parameter overload | ||
| /// cannot infer <typeparamref name="TItem"/> there, because C# does not infer from constraints. | ||
| /// </summary> | ||
| public static ShouldSingleItemAssertion<IEnumerable<TItem>, TItem> HaveSingleItem<TItem>( | ||
| this IShouldSource<IEnumerable<TItem>> source) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Support set continuations.
Add inferable overloads for set continuations. Cover
|
||
| => source.HaveSingleItem<IEnumerable<TItem>, TItem>(); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.