Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions TUnit.Assertions.Tests/Bugs/Issue3580Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
namespace TUnit.Assertions.Tests.Bugs;

/// <summary>
/// Tests for issue #3580: More warnings CS8620 and CS8619 after update to >= 0.72
/// https://github.com/thomhurst/TUnit/issues/3580
/// </summary>
public class Issue3580Tests
{
[Test]
public async Task Task_FromResult_With_NonNullable_Value_And_Nullable_Expected_Should_Not_Cause_Warnings()
{
// Scenario 1: Both parameters nullable (from issue)
object? value = "test";
object? expected = "test";

// This should not produce CS8620 or CS8619 warnings
await Assert.That(Task.FromResult(value)).IsEqualTo(expected);
}

[Test]
public async Task Task_FromResult_With_NonNullable_Value_And_NonNullable_Expected_Should_Not_Cause_Warnings()
{
// Scenario 2: Non-nullable value with nullable expectation (from issue)
object value = "test";
object? expected = "test";

// This should not produce CS8620 or CS8619 warnings
await Assert.That(Task.FromResult(value)).IsEqualTo(expected);
}

[Test]
public async Task Task_FromResult_With_Both_NonNullable_Should_Not_Cause_Warnings()
{
// Scenario 3: Both non-nullable (from issue)
object value = "test";
object expected = "test";

// This should not produce CS8619 warnings
await Assert.That(Task.FromResult(value)).IsEqualTo(expected);
}

[Test]
public async Task Task_FromResult_With_String_Should_Not_Cause_Warnings()
{
// Test with string type (reference type)
var value = "hello";
var expected = "hello";

await Assert.That(Task.FromResult(value)).IsEqualTo(expected);
}

[Test]
public async Task Task_FromResult_With_ValueType_Should_Not_Cause_Warnings()
{
// Test with value type (int)
var value = 42;
var expected = 42;

await Assert.That(Task.FromResult(value)).IsEqualTo(expected);
}

[Test]
public async Task Task_FromResult_With_Null_Value_Should_Work()
{
// Test with null value
object? value = null;

var result = await Task.FromResult(value);
await Assert.That(result).IsNull();
}

[Test]
public async Task Task_FromResult_With_Custom_Type_Should_Not_Cause_Warnings()
{
// Test with custom type
var value = new TestData { Value = "test" };
var expected = value;

var result = await Task.FromResult(value);
await Assert.That(result).IsSameReferenceAs(expected);
}

[Test]
public async Task Async_Method_Returning_NonNullable_Task_Should_Not_Cause_Warnings()
{
// Test with async method that returns non-nullable task
// Just verify we can await it and get a non-null result
var result = await GetNonNullableValueAsync();

await Assert.That(result).IsNotNull();
}

[Test]
public async Task Task_FromResult_With_IsNotNull_Should_Not_Cause_Warnings()
{
// Test IsNotNull assertion
object value = "test";

var result = await Task.FromResult(value);
await Assert.That(result).IsNotNull();
}

[Test]
public async Task Task_FromResult_With_IsGreaterThan_Should_Not_Cause_Warnings()
{
// Test numeric comparison
var value = 10;

await Assert.That(Task.FromResult(value)).IsGreaterThan(5);
}

private static async Task<object> GetNonNullableValueAsync()
{
await Task.Yield();
return new object();
}

private class TestData
{
public string Value { get; set; } = string.Empty;
}
}
2 changes: 1 addition & 1 deletion TUnit.Assertions.Tests/Old/EqualsAssertionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ await TUnitAssert.That(async () =>
await TUnitAssert.That(one).IsEqualTo("2", StringComparison.Ordinal).And.IsNotEqualTo("1").And.IsOfType(typeof(string))
).ThrowsException()
.And
.HasMessageContaining("Assert.That(one).IsEqualTo(\"2\", Ordinal)");
.HasMessageContaining("Assert.That(one).IsEqualTo(\"2\", StringComparison.Ordinal)");
}

[Test]
Expand Down
16 changes: 13 additions & 3 deletions TUnit.Assertions/Conditions/StringEqualsAssertion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ public class StringEqualsAssertion : Assertion<string>
{
private readonly string? _expected;
private StringComparison _comparison = StringComparison.Ordinal;
private bool _trimming = false;
private bool _nullAndEmptyEquality = false;
private bool _ignoringWhitespace = false;
private bool _trimming;
private bool _nullAndEmptyEquality;
private bool _ignoringWhitespace;

public StringEqualsAssertion(
AssertionContext<string> context,
Expand All @@ -25,6 +25,16 @@ public StringEqualsAssertion(
_expected = expected;
}

public StringEqualsAssertion(
AssertionContext<string> context,
string? expected,
StringComparison comparison)
: base(context)
{
_expected = expected;
_comparison = comparison;
}

/// <summary>
/// Makes the comparison case-insensitive.
/// </summary>
Expand Down
13 changes: 11 additions & 2 deletions TUnit.Assertions/Extensions/Assert.cs
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,23 @@ public static AsyncFuncAssertion<TValue> That<TValue>(
/// <summary>
/// Creates an assertion for a Task that returns a value.
/// Supports both result assertions (e.g., IsEqualTo) and task state assertions (e.g., IsCompleted).
/// This method accepts both nullable and non-nullable Task results seamlessly.
/// Example: await Assert.That(GetValueAsync()).IsEqualTo(expected);
/// Example: await Assert.That(Task.FromResult(value)).IsEqualTo(expected);
/// Example: await Assert.That(GetValueAsync()).IsCompleted();
/// </summary>
public static TaskAssertion<TValue> That<TValue>(
Task<TValue?> task,
Task<TValue> task,
[CallerArgumentExpression(nameof(task))] string? expression = null)
{
return new TaskAssertion<TValue>(task, expression);
var nullableTask = ConvertToNullableTask(task);
return new TaskAssertion<TValue>(nullableTask, expression);

static async Task<TValue?> ConvertToNullableTask(Task<TValue> sourceTask)
{
var result = await sourceTask.ConfigureAwait(false);
return result;
}
}

/// <summary>
Expand Down
15 changes: 0 additions & 15 deletions TUnit.Assertions/Extensions/AssertionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,6 @@ public static EqualsAssertion<TValue> EqualTo<TValue>(
return new EqualsAssertion<TValue>(source.Context, expected);
}

/// <summary>
/// Asserts that the string is equal to the expected value using the specified comparison.
/// Uses .WithComparison() since StringEqualsAssertion doesn't have a constructor for this.
/// </summary>
public static StringEqualsAssertion IsEqualTo(
this IAssertionSource<string> source,
string? expected,
StringComparison comparison,
[CallerArgumentExpression(nameof(expected))] string? expression = null)
{
source.Context.ExpressionBuilder.Append($".IsEqualTo({expression}, {comparison})");
var assertion = new StringEqualsAssertion(source.Context, expected);
return assertion.WithComparison(comparison);
}

/// <summary>
/// Asserts that the numeric value is greater than zero (positive).
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace
public static .<TItem> That<TItem>(.<TItem>? value, [.("value")] string? expression = null) { }
public static .<TValue> That<TValue>(<.<TValue?>> func, [.("func")] string? expression = null) { }
public static .<TValue> That<TValue>(<TValue?> func, [.("func")] string? expression = null) { }
public static .<TValue> That<TValue>(.<TValue?> task, [.("task")] string? expression = null) { }
public static .<TValue> That<TValue>(.<TValue> task, [.("task")] string? expression = null) { }
public static .<TValue> That<TValue>(TValue? value, [.("value")] string? expression = null) { }
[.(3)]
public static .<TKey, TValue> That<TKey, TValue>(.<TKey, TValue> value, [.("value")] string? expression = null) { }
Expand Down Expand Up @@ -1400,6 +1400,7 @@ namespace .Conditions
public class StringEqualsAssertion : .<string>
{
public StringEqualsAssertion(.<string> context, string? expected) { }
public StringEqualsAssertion(.<string> context, string? expected, comparison) { }
protected override .<.> CheckAsync(.<string> metadata) { }
protected override string GetExpectation() { }
public . IgnoringCase() { }
Expand Down Expand Up @@ -1977,7 +1978,6 @@ namespace .Extensions
public static .<> IsBeforeOrEqualTo(this .<> source, expected, [.("expected")] string? expression = null) { }
public static ..IsDefinedAssertion<TEnum> IsDefined<TEnum>(this .<TEnum> source)
where TEnum : struct, { }
public static . IsEqualTo(this .<string> source, string? expected, comparison, [.("expected")] string? expression = null) { }
[.("Uses reflection to compare members")]
public static .<TValue> IsEquivalentTo<TValue>(this .<TValue> source, object? expected, [.("expected")] string? expression = null) { }
public static .<TValue> IsNegative<TValue>(this .<TValue> source)
Expand Down Expand Up @@ -3760,6 +3760,8 @@ namespace .Extensions
{
[.(2)]
public static . IsEqualTo(this .<string> source, string? expected, [.("expected")] string? expectedExpression = null) { }
[.(2)]
public static . IsEqualTo(this .<string> source, string? expected, comparison, [.("expected")] string? expectedExpression = null, [.("comparison")] string? comparisonExpression = null) { }
}
public static class StringIsEmptyAssertionExtensions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace
public static .<TItem> That<TItem>(.<TItem>? value, [.("value")] string? expression = null) { }
public static .<TValue> That<TValue>(<.<TValue?>> func, [.("func")] string? expression = null) { }
public static .<TValue> That<TValue>(<TValue?> func, [.("func")] string? expression = null) { }
public static .<TValue> That<TValue>(.<TValue?> task, [.("task")] string? expression = null) { }
public static .<TValue> That<TValue>(.<TValue> task, [.("task")] string? expression = null) { }
public static .<TValue> That<TValue>(TValue? value, [.("value")] string? expression = null) { }
public static .<TKey, TValue> That<TKey, TValue>(.<TKey, TValue> value, [.("value")] string? expression = null) { }
public static Throws( exceptionType, action) { }
Expand Down Expand Up @@ -1397,6 +1397,7 @@ namespace .Conditions
public class StringEqualsAssertion : .<string>
{
public StringEqualsAssertion(.<string> context, string? expected) { }
public StringEqualsAssertion(.<string> context, string? expected, comparison) { }
protected override .<.> CheckAsync(.<string> metadata) { }
protected override string GetExpectation() { }
public . IgnoringCase() { }
Expand Down Expand Up @@ -1974,7 +1975,6 @@ namespace .Extensions
public static .<> IsBeforeOrEqualTo(this .<> source, expected, [.("expected")] string? expression = null) { }
public static ..IsDefinedAssertion<TEnum> IsDefined<TEnum>(this .<TEnum> source)
where TEnum : struct, { }
public static . IsEqualTo(this .<string> source, string? expected, comparison, [.("expected")] string? expression = null) { }
[.("Uses reflection to compare members")]
public static .<TValue> IsEquivalentTo<TValue>(this .<TValue> source, object? expected, [.("expected")] string? expression = null) { }
public static .<TValue> IsNegative<TValue>(this .<TValue> source)
Expand Down Expand Up @@ -3741,6 +3741,7 @@ namespace .Extensions
public static class StringEqualsAssertionExtensions
{
public static . IsEqualTo(this .<string> source, string? expected, [.("expected")] string? expectedExpression = null) { }
public static . IsEqualTo(this .<string> source, string? expected, comparison, [.("expected")] string? expectedExpression = null, [.("comparison")] string? comparisonExpression = null) { }
}
public static class StringIsEmptyAssertionExtensions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ namespace
public static .<TItem> That<TItem>(.<TItem>? value, [.("value")] string? expression = null) { }
public static .<TValue> That<TValue>(<.<TValue?>> func, [.("func")] string? expression = null) { }
public static .<TValue> That<TValue>(<TValue?> func, [.("func")] string? expression = null) { }
public static .<TValue> That<TValue>(.<TValue?> task, [.("task")] string? expression = null) { }
public static .<TValue> That<TValue>(.<TValue> task, [.("task")] string? expression = null) { }
public static .<TValue> That<TValue>(TValue? value, [.("value")] string? expression = null) { }
[.(3)]
public static .<TKey, TValue> That<TKey, TValue>(.<TKey, TValue> value, [.("value")] string? expression = null) { }
Expand Down Expand Up @@ -1400,6 +1400,7 @@ namespace .Conditions
public class StringEqualsAssertion : .<string>
{
public StringEqualsAssertion(.<string> context, string? expected) { }
public StringEqualsAssertion(.<string> context, string? expected, comparison) { }
protected override .<.> CheckAsync(.<string> metadata) { }
protected override string GetExpectation() { }
public . IgnoringCase() { }
Expand Down Expand Up @@ -1977,7 +1978,6 @@ namespace .Extensions
public static .<> IsBeforeOrEqualTo(this .<> source, expected, [.("expected")] string? expression = null) { }
public static ..IsDefinedAssertion<TEnum> IsDefined<TEnum>(this .<TEnum> source)
where TEnum : struct, { }
public static . IsEqualTo(this .<string> source, string? expected, comparison, [.("expected")] string? expression = null) { }
[.("Uses reflection to compare members")]
public static .<TValue> IsEquivalentTo<TValue>(this .<TValue> source, object? expected, [.("expected")] string? expression = null) { }
public static .<TValue> IsNegative<TValue>(this .<TValue> source)
Expand Down Expand Up @@ -3760,6 +3760,8 @@ namespace .Extensions
{
[.(2)]
public static . IsEqualTo(this .<string> source, string? expected, [.("expected")] string? expectedExpression = null) { }
[.(2)]
public static . IsEqualTo(this .<string> source, string? expected, comparison, [.("expected")] string? expectedExpression = null, [.("comparison")] string? comparisonExpression = null) { }
}
public static class StringIsEmptyAssertionExtensions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace
public static .<TItem> That<TItem>(.<TItem>? value, [.("value")] string? expression = null) { }
public static .<TValue> That<TValue>(<.<TValue?>> func, [.("func")] string? expression = null) { }
public static .<TValue> That<TValue>(<TValue?> func, [.("func")] string? expression = null) { }
public static .<TValue> That<TValue>(.<TValue?> task, [.("task")] string? expression = null) { }
public static .<TValue> That<TValue>(.<TValue> task, [.("task")] string? expression = null) { }
public static .<TValue> That<TValue>(TValue? value, [.("value")] string? expression = null) { }
public static .<TKey, TValue> That<TKey, TValue>(.<TKey, TValue> value, [.("value")] string? expression = null) { }
public static Throws( exceptionType, action) { }
Expand Down Expand Up @@ -1285,6 +1285,7 @@ namespace .Conditions
public class StringEqualsAssertion : .<string>
{
public StringEqualsAssertion(.<string> context, string? expected) { }
public StringEqualsAssertion(.<string> context, string? expected, comparison) { }
protected override .<.> CheckAsync(.<string> metadata) { }
protected override string GetExpectation() { }
public . IgnoringCase() { }
Expand Down Expand Up @@ -1806,7 +1807,6 @@ namespace .Extensions
public static .<> IsBeforeOrEqualTo(this .<> source, expected, [.("expected")] string? expression = null) { }
public static ..IsDefinedAssertion<TEnum> IsDefined<TEnum>(this .<TEnum> source)
where TEnum : struct, { }
public static . IsEqualTo(this .<string> source, string? expected, comparison, [.("expected")] string? expression = null) { }
public static .<TValue> IsEquivalentTo<TValue>(this .<TValue> source, object? expected, [.("expected")] string? expression = null) { }
public static .<TValue> IsNegative<TValue>(this .<TValue> source)
where TValue : <TValue> { }
Expand Down Expand Up @@ -3256,6 +3256,7 @@ namespace .Extensions
public static class StringEqualsAssertionExtensions
{
public static . IsEqualTo(this .<string> source, string? expected, [.("expected")] string? expectedExpression = null) { }
public static . IsEqualTo(this .<string> source, string? expected, comparison, [.("expected")] string? expectedExpression = null, [.("comparison")] string? comparisonExpression = null) { }
}
public static class StringIsEmptyAssertionExtensions
{
Expand Down
Loading