diff --git a/TUnit.Analyzers.CodeFixers/MSTestMigrationCodeFixProvider.cs b/TUnit.Analyzers.CodeFixers/MSTestMigrationCodeFixProvider.cs index a636035820..763773508e 100644 --- a/TUnit.Analyzers.CodeFixers/MSTestMigrationCodeFixProvider.cs +++ b/TUnit.Analyzers.CodeFixers/MSTestMigrationCodeFixProvider.cs @@ -283,13 +283,13 @@ protected override bool IsFrameworkAssertionNamespace(string namespaceName) "AreEqual" => ConvertAreEqual(arguments), "AreNotEqual" => ConvertAreNotEqual(arguments), "AreSame" when arguments.Count >= 3 => - CreateTUnitAssertionWithMessage("IsSameReference", arguments[1].Expression, arguments[2].Expression, arguments[0]), + CreateTUnitAssertionWithMessage("IsSameReferenceAs", arguments[1].Expression, arguments[2].Expression, arguments[0]), "AreSame" when arguments.Count >= 2 => - CreateTUnitAssertion("IsSameReference", arguments[1].Expression, arguments[0]), + CreateTUnitAssertion("IsSameReferenceAs", arguments[1].Expression, arguments[0]), "AreNotSame" when arguments.Count >= 3 => - CreateTUnitAssertionWithMessage("IsNotSameReference", arguments[1].Expression, arguments[2].Expression, arguments[0]), + CreateTUnitAssertionWithMessage("IsNotSameReferenceAs", arguments[1].Expression, arguments[2].Expression, arguments[0]), "AreNotSame" when arguments.Count >= 2 => - CreateTUnitAssertion("IsNotSameReference", arguments[1].Expression, arguments[0]), + CreateTUnitAssertion("IsNotSameReferenceAs", arguments[1].Expression, arguments[0]), // 1-arg assertions with message as 2nd param "IsTrue" when arguments.Count >= 2 => diff --git a/TUnit.Analyzers.CodeFixers/NUnitMigrationCodeFixProvider.cs b/TUnit.Analyzers.CodeFixers/NUnitMigrationCodeFixProvider.cs index a81ead26fe..fb7995a29a 100644 --- a/TUnit.Analyzers.CodeFixers/NUnitMigrationCodeFixProvider.cs +++ b/TUnit.Analyzers.CodeFixers/NUnitMigrationCodeFixProvider.cs @@ -537,6 +537,20 @@ protected override bool IsFrameworkAssertionNamespace(string namespaceName) return ConvertDirectoryAssertion(invocation, directoryAccess.Name.Identifier.Text); } + // Handle CollectionAssert + if (invocation.Expression is MemberAccessExpressionSyntax collectionAccess && + collectionAccess.Expression is IdentifierNameSyntax { Identifier.Text: "CollectionAssert" }) + { + return ConvertCollectionAssertion(invocation, collectionAccess.Name.Identifier.Text); + } + + // Handle StringAssert + if (invocation.Expression is MemberAccessExpressionSyntax stringAccess && + stringAccess.Expression is IdentifierNameSyntax { Identifier.Text: "StringAssert" }) + { + return ConvertStringAssertion(invocation, stringAccess.Name.Identifier.Text); + } + if (!IsFrameworkAssertion(invocation)) { return null; @@ -761,9 +775,9 @@ exactlyInvocation.Expression is MemberAccessExpressionSyntax exactlyMemberAccess { return memberName switch { - "Ascending" => CreateTUnitAssertionWithMessage("IsInAscendingOrder", actualValue, message), + "Ascending" => CreateTUnitAssertionWithMessage("IsInOrder", actualValue, message), "Descending" => CreateTUnitAssertionWithMessage("IsInDescendingOrder", actualValue, message), - _ => CreateTUnitAssertionWithMessage("IsInAscendingOrder", actualValue, message) // Default to ascending for Is.Ordered + _ => CreateTUnitAssertionWithMessage("IsInOrder", actualValue, message) // Default to ascending for Is.Ordered }; } @@ -797,7 +811,7 @@ exactlyInvocation.Expression is MemberAccessExpressionSyntax exactlyMemberAccess "Zero" => CreateTUnitAssertionWithMessage("IsZero", actualValue, message), "NaN" => CreateTUnitAssertionWithMessage("IsNaN", actualValue, message), "Unique" => CreateTUnitAssertionWithMessage("HasDistinctItems", actualValue, message), - "Ordered" => CreateTUnitAssertionWithMessage("IsInAscendingOrder", actualValue, message), + "Ordered" => CreateTUnitAssertionWithMessage("IsInOrder", actualValue, message), _ => CreateTUnitAssertionWithMessage("IsEqualTo", actualValue, message, SyntaxFactory.Argument(constraint)) }; } @@ -963,9 +977,9 @@ exactlyInvocation.Expression is MemberAccessExpressionSyntax exactlyMemberAccess { return memberName switch { - "Ascending" => CreateTUnitAssertion("IsInAscendingOrder", actualValue), + "Ascending" => CreateTUnitAssertion("IsInOrder", actualValue), "Descending" => CreateTUnitAssertion("IsInDescendingOrder", actualValue), - _ => CreateTUnitAssertion("IsInAscendingOrder", actualValue) // Default to ascending for Is.Ordered + _ => CreateTUnitAssertion("IsInOrder", actualValue) // Default to ascending for Is.Ordered }; } @@ -999,7 +1013,7 @@ exactlyInvocation.Expression is MemberAccessExpressionSyntax exactlyMemberAccess "Zero" => CreateTUnitAssertion("IsZero", actualValue), "NaN" => CreateTUnitAssertion("IsNaN", actualValue), "Unique" => CreateTUnitAssertion("HasDistinctItems", actualValue), - "Ordered" => CreateTUnitAssertion("IsInAscendingOrder", actualValue), + "Ordered" => CreateTUnitAssertion("IsInOrder", actualValue), _ => CreateTUnitAssertion("IsEqualTo", actualValue, SyntaxFactory.Argument(constraint)) }; } @@ -1665,6 +1679,104 @@ private static ExpressionSyntax CreateDirectoryInfoExpression(ExpressionSyntax p return pathOrDirectoryInfo; } + private ExpressionSyntax? ConvertCollectionAssertion(InvocationExpressionSyntax invocation, string methodName) + { + var arguments = invocation.ArgumentList.Arguments; + + // CollectionAssert methods - note the argument order varies by method + return methodName switch + { + // CollectionAssert.AreEqual(expected, actual) -> Assert.That(actual).IsEquivalentTo(expected) + "AreEqual" when arguments.Count >= 2 => CreateTUnitAssertion("IsEquivalentTo", arguments[1].Expression, SyntaxFactory.Argument(arguments[0].Expression)), + // CollectionAssert.AreNotEqual(expected, actual) -> Assert.That(actual).IsNotEquivalentTo(expected) + "AreNotEqual" when arguments.Count >= 2 => CreateTUnitAssertion("IsNotEquivalentTo", arguments[1].Expression, SyntaxFactory.Argument(arguments[0].Expression)), + // CollectionAssert.AreEquivalent(expected, actual) -> Assert.That(actual).IsEquivalentTo(expected) + "AreEquivalent" when arguments.Count >= 2 => CreateTUnitAssertion("IsEquivalentTo", arguments[1].Expression, SyntaxFactory.Argument(arguments[0].Expression)), + // CollectionAssert.AreNotEquivalent(expected, actual) -> Assert.That(actual).IsNotEquivalentTo(expected) + "AreNotEquivalent" when arguments.Count >= 2 => CreateTUnitAssertion("IsNotEquivalentTo", arguments[1].Expression, SyntaxFactory.Argument(arguments[0].Expression)), + // CollectionAssert.Contains(collection, item) -> Assert.That(collection).Contains(item) + "Contains" when arguments.Count >= 2 => CreateTUnitAssertion("Contains", arguments[0].Expression, SyntaxFactory.Argument(arguments[1].Expression)), + // CollectionAssert.DoesNotContain(collection, item) -> Assert.That(collection).DoesNotContain(item) + "DoesNotContain" when arguments.Count >= 2 => CreateTUnitAssertion("DoesNotContain", arguments[0].Expression, SyntaxFactory.Argument(arguments[1].Expression)), + // CollectionAssert.IsEmpty(collection) -> Assert.That(collection).IsEmpty() + "IsEmpty" when arguments.Count >= 1 => CreateTUnitAssertion("IsEmpty", arguments[0].Expression), + // CollectionAssert.IsNotEmpty(collection) -> Assert.That(collection).IsNotEmpty() + "IsNotEmpty" when arguments.Count >= 1 => CreateTUnitAssertion("IsNotEmpty", arguments[0].Expression), + // CollectionAssert.AllItemsAreUnique(collection) -> Assert.That(collection).HasDistinctItems() + "AllItemsAreUnique" when arguments.Count >= 1 => CreateTUnitAssertion("HasDistinctItems", arguments[0].Expression), + // CollectionAssert.IsOrdered(collection) -> Assert.That(collection).IsInOrder() + "IsOrdered" when arguments.Count >= 1 => CreateTUnitAssertion("IsInOrder", arguments[0].Expression), + // CollectionAssert.IsSubsetOf(subset, superset) -> Assert.That(subset).IsSubsetOf(superset) + "IsSubsetOf" when arguments.Count >= 2 => CreateTUnitAssertion("IsSubsetOf", arguments[0].Expression, SyntaxFactory.Argument(arguments[1].Expression)), + // CollectionAssert.IsSupersetOf(superset, subset) -> Assert.That(superset).IsSupersetOf(subset) + "IsSupersetOf" when arguments.Count >= 2 => CreateTUnitAssertion("IsSupersetOf", arguments[0].Expression, SyntaxFactory.Argument(arguments[1].Expression)), + // CollectionAssert.AllItemsAreNotNull(collection) -> Assert.That(collection).DoesNotContain(null) + "AllItemsAreNotNull" when arguments.Count >= 1 => CreateTUnitAssertion("DoesNotContain", arguments[0].Expression, SyntaxFactory.Argument(SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression))), + _ => null + }; + } + + private ExpressionSyntax? ConvertStringAssertion(InvocationExpressionSyntax invocation, string methodName) + { + var arguments = invocation.ArgumentList.Arguments; + + // StringAssert methods - note: NUnit uses (expected, actual) order + return methodName switch + { + // StringAssert.Contains(expected, actual) -> Assert.That(actual).Contains(expected) + "Contains" when arguments.Count >= 2 => CreateTUnitAssertion("Contains", arguments[1].Expression, SyntaxFactory.Argument(arguments[0].Expression)), + // StringAssert.DoesNotContain(expected, actual) -> Assert.That(actual).DoesNotContain(expected) + "DoesNotContain" when arguments.Count >= 2 => CreateTUnitAssertion("DoesNotContain", arguments[1].Expression, SyntaxFactory.Argument(arguments[0].Expression)), + // StringAssert.StartsWith(expected, actual) -> Assert.That(actual).StartsWith(expected) + "StartsWith" when arguments.Count >= 2 => CreateTUnitAssertion("StartsWith", arguments[1].Expression, SyntaxFactory.Argument(arguments[0].Expression)), + // StringAssert.DoesNotStartWith(expected, actual) -> Assert.That(actual).DoesNotStartWith(expected) + "DoesNotStartWith" when arguments.Count >= 2 => CreateTUnitAssertion("DoesNotStartWith", arguments[1].Expression, SyntaxFactory.Argument(arguments[0].Expression)), + // StringAssert.EndsWith(expected, actual) -> Assert.That(actual).EndsWith(expected) + "EndsWith" when arguments.Count >= 2 => CreateTUnitAssertion("EndsWith", arguments[1].Expression, SyntaxFactory.Argument(arguments[0].Expression)), + // StringAssert.DoesNotEndWith(expected, actual) -> Assert.That(actual).DoesNotEndWith(expected) + "DoesNotEndWith" when arguments.Count >= 2 => CreateTUnitAssertion("DoesNotEndWith", arguments[1].Expression, SyntaxFactory.Argument(arguments[0].Expression)), + // StringAssert.IsMatch(pattern, actual) -> Assert.That(actual).Matches(pattern) + "IsMatch" when arguments.Count >= 2 => CreateTUnitAssertion("Matches", arguments[1].Expression, SyntaxFactory.Argument(arguments[0].Expression)), + // StringAssert.DoesNotMatch(pattern, actual) -> Assert.That(actual).DoesNotMatch(pattern) + "DoesNotMatch" when arguments.Count >= 2 => CreateTUnitAssertion("DoesNotMatch", arguments[1].Expression, SyntaxFactory.Argument(arguments[0].Expression)), + // StringAssert.AreEqualIgnoringCase(expected, actual) -> Assert.That(actual).IsEqualTo(expected, StringComparison.OrdinalIgnoreCase) + "AreEqualIgnoringCase" when arguments.Count >= 2 => CreateStringComparisonAssertion("IsEqualTo", arguments[1].Expression, arguments[0].Expression), + // StringAssert.AreNotEqualIgnoringCase(expected, actual) -> Assert.That(actual).IsNotEqualTo(expected, StringComparison.OrdinalIgnoreCase) + "AreNotEqualIgnoringCase" when arguments.Count >= 2 => CreateStringComparisonAssertion("IsNotEqualTo", arguments[1].Expression, arguments[0].Expression), + _ => null + }; + } + + private ExpressionSyntax CreateStringComparisonAssertion(string methodName, ExpressionSyntax actual, ExpressionSyntax expected) + { + // Create Assert.That(actual) + var assertThatInvocation = SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName("Assert"), + SyntaxFactory.IdentifierName("That")), + SyntaxFactory.ArgumentList( + SyntaxFactory.SingletonSeparatedList(SyntaxFactory.Argument(actual)))); + + // Create Assert.That(actual).IsEqualTo(expected, StringComparison.OrdinalIgnoreCase) + var stringComparisonArg = SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName("StringComparison"), + SyntaxFactory.IdentifierName("OrdinalIgnoreCase")); + + return SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + assertThatInvocation, + SyntaxFactory.IdentifierName(methodName)), + SyntaxFactory.ArgumentList( + SyntaxFactory.SeparatedList(new[] + { + SyntaxFactory.Argument(expected), + SyntaxFactory.Argument(stringComparisonArg) + }))); + } + } public class NUnitBaseTypeRewriter : CSharpSyntaxRewriter diff --git a/TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs b/TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs index f4ab66281b..6858434068 100644 --- a/TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs +++ b/TUnit.Analyzers.Tests/MSTestMigrationAnalyzerTests.cs @@ -686,8 +686,8 @@ public async Task TestReferences() var obj2 = obj1; var obj3 = new object(); - await Assert.That(obj2).IsSameReference(obj1); - await Assert.That(obj3).IsNotSameReference(obj1); + await Assert.That(obj2).IsSameReferenceAs(obj1); + await Assert.That(obj3).IsNotSameReferenceAs(obj1); } } """, diff --git a/TUnit.Analyzers.Tests/NUnitMigrationAnalyzerTests.cs b/TUnit.Analyzers.Tests/NUnitMigrationAnalyzerTests.cs index c07e96f02c..e9e40b6223 100644 --- a/TUnit.Analyzers.Tests/NUnitMigrationAnalyzerTests.cs +++ b/TUnit.Analyzers.Tests/NUnitMigrationAnalyzerTests.cs @@ -3715,7 +3715,7 @@ public class MyClass public async Task TestMethod() { var list = new List { 1, 2, 3 }; - await Assert.That(list).IsInAscendingOrder(); + await Assert.That(list).IsInOrder(); } } """, @@ -3797,7 +3797,7 @@ public class MyClass public async Task TestMethod() { var list = new List { 1, 2, 3 }; - await Assert.That(list).IsInAscendingOrder(); + await Assert.That(list).IsInOrder(); } } """, @@ -3846,6 +3846,398 @@ public async Task TestMethod() ); } + [Test] + public async Task NUnit_CollectionAssert_Contains_Converted() + { + await CodeFixer.VerifyCodeFixAsync( + """ + using NUnit.Framework; + using System.Collections.Generic; + + {|#0:public class MyClass|} + { + [Test] + public void TestMethod() + { + var list = new List { 1, 2, 3 }; + CollectionAssert.Contains(list, 2); + } + } + """, + Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), + """ + using System.Collections.Generic; + using System.Threading.Tasks; + using TUnit.Core; + using TUnit.Assertions; + using static TUnit.Assertions.Assert; + using TUnit.Assertions.Extensions; + + public class MyClass + { + [Test] + public async Task TestMethod() + { + var list = new List { 1, 2, 3 }; + await Assert.That(list).Contains(2); + } + } + """, + ConfigureNUnitTest + ); + } + + [Test] + public async Task NUnit_CollectionAssert_DoesNotContain_Converted() + { + await CodeFixer.VerifyCodeFixAsync( + """ + using NUnit.Framework; + using System.Collections.Generic; + + {|#0:public class MyClass|} + { + [Test] + public void TestMethod() + { + var list = new List { 1, 2, 3 }; + CollectionAssert.DoesNotContain(list, 5); + } + } + """, + Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), + """ + using System.Collections.Generic; + using System.Threading.Tasks; + using TUnit.Core; + using TUnit.Assertions; + using static TUnit.Assertions.Assert; + using TUnit.Assertions.Extensions; + + public class MyClass + { + [Test] + public async Task TestMethod() + { + var list = new List { 1, 2, 3 }; + await Assert.That(list).DoesNotContain(5); + } + } + """, + ConfigureNUnitTest + ); + } + + [Test] + public async Task NUnit_CollectionAssert_IsEmpty_Converted() + { + await CodeFixer.VerifyCodeFixAsync( + """ + using NUnit.Framework; + using System.Collections.Generic; + + {|#0:public class MyClass|} + { + [Test] + public void TestMethod() + { + var list = new List(); + CollectionAssert.IsEmpty(list); + } + } + """, + Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), + """ + using System.Collections.Generic; + using System.Threading.Tasks; + using TUnit.Core; + using TUnit.Assertions; + using static TUnit.Assertions.Assert; + using TUnit.Assertions.Extensions; + + public class MyClass + { + [Test] + public async Task TestMethod() + { + var list = new List(); + await Assert.That(list).IsEmpty(); + } + } + """, + ConfigureNUnitTest + ); + } + + [Test] + public async Task NUnit_CollectionAssert_AreEquivalent_Converted() + { + await CodeFixer.VerifyCodeFixAsync( + """ + using NUnit.Framework; + using System.Collections.Generic; + + {|#0:public class MyClass|} + { + [Test] + public void TestMethod() + { + var expected = new List { 1, 2, 3 }; + var actual = new List { 3, 2, 1 }; + CollectionAssert.AreEquivalent(expected, actual); + } + } + """, + Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), + """ + using System.Collections.Generic; + using System.Threading.Tasks; + using TUnit.Core; + using TUnit.Assertions; + using static TUnit.Assertions.Assert; + using TUnit.Assertions.Extensions; + + public class MyClass + { + [Test] + public async Task TestMethod() + { + var expected = new List { 1, 2, 3 }; + var actual = new List { 3, 2, 1 }; + await Assert.That(actual).IsEquivalentTo(expected); + } + } + """, + ConfigureNUnitTest + ); + } + + [Test] + public async Task NUnit_CollectionAssert_AllItemsAreUnique_Converted() + { + await CodeFixer.VerifyCodeFixAsync( + """ + using NUnit.Framework; + using System.Collections.Generic; + + {|#0:public class MyClass|} + { + [Test] + public void TestMethod() + { + var list = new List { 1, 2, 3 }; + CollectionAssert.AllItemsAreUnique(list); + } + } + """, + Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), + """ + using System.Collections.Generic; + using System.Threading.Tasks; + using TUnit.Core; + using TUnit.Assertions; + using static TUnit.Assertions.Assert; + using TUnit.Assertions.Extensions; + + public class MyClass + { + [Test] + public async Task TestMethod() + { + var list = new List { 1, 2, 3 }; + await Assert.That(list).HasDistinctItems(); + } + } + """, + ConfigureNUnitTest + ); + } + + [Test] + public async Task NUnit_StringAssert_Contains_Converted() + { + await CodeFixer.VerifyCodeFixAsync( + """ + using NUnit.Framework; + + {|#0:public class MyClass|} + { + [Test] + public void TestMethod() + { + StringAssert.Contains("world", "hello world"); + } + } + """, + Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), + """ + using System.Threading.Tasks; + using TUnit.Core; + using TUnit.Assertions; + using static TUnit.Assertions.Assert; + using TUnit.Assertions.Extensions; + + public class MyClass + { + [Test] + public async Task TestMethod() + { + await Assert.That("hello world").Contains("world"); + } + } + """, + ConfigureNUnitTest + ); + } + + [Test] + public async Task NUnit_StringAssert_StartsWith_Converted() + { + await CodeFixer.VerifyCodeFixAsync( + """ + using NUnit.Framework; + + {|#0:public class MyClass|} + { + [Test] + public void TestMethod() + { + StringAssert.StartsWith("hello", "hello world"); + } + } + """, + Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), + """ + using System.Threading.Tasks; + using TUnit.Core; + using TUnit.Assertions; + using static TUnit.Assertions.Assert; + using TUnit.Assertions.Extensions; + + public class MyClass + { + [Test] + public async Task TestMethod() + { + await Assert.That("hello world").StartsWith("hello"); + } + } + """, + ConfigureNUnitTest + ); + } + + [Test] + public async Task NUnit_StringAssert_EndsWith_Converted() + { + await CodeFixer.VerifyCodeFixAsync( + """ + using NUnit.Framework; + + {|#0:public class MyClass|} + { + [Test] + public void TestMethod() + { + StringAssert.EndsWith("world", "hello world"); + } + } + """, + Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), + """ + using System.Threading.Tasks; + using TUnit.Core; + using TUnit.Assertions; + using static TUnit.Assertions.Assert; + using TUnit.Assertions.Extensions; + + public class MyClass + { + [Test] + public async Task TestMethod() + { + await Assert.That("hello world").EndsWith("world"); + } + } + """, + ConfigureNUnitTest + ); + } + + [Test] + public async Task NUnit_StringAssert_IsMatch_Converted() + { + await CodeFixer.VerifyCodeFixAsync( + """ + using NUnit.Framework; + + {|#0:public class MyClass|} + { + [Test] + public void TestMethod() + { + StringAssert.IsMatch("[0-9]+", "123"); + } + } + """, + Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), + """ + using System.Threading.Tasks; + using TUnit.Core; + using TUnit.Assertions; + using static TUnit.Assertions.Assert; + using TUnit.Assertions.Extensions; + + public class MyClass + { + [Test] + public async Task TestMethod() + { + await Assert.That("123").Matches("[0-9]+"); + } + } + """, + ConfigureNUnitTest + ); + } + + [Test] + public async Task NUnit_StringAssert_DoesNotMatch_Converted() + { + await CodeFixer.VerifyCodeFixAsync( + """ + using NUnit.Framework; + + {|#0:public class MyClass|} + { + [Test] + public void TestMethod() + { + StringAssert.DoesNotMatch("[0-9]+", "abc"); + } + } + """, + Verifier.Diagnostic(Rules.NUnitMigration).WithLocation(0), + """ + using System.Threading.Tasks; + using TUnit.Core; + using TUnit.Assertions; + using static TUnit.Assertions.Assert; + using TUnit.Assertions.Extensions; + + public class MyClass + { + [Test] + public async Task TestMethod() + { + await Assert.That("abc").DoesNotMatch("[0-9]+"); + } + } + """, + ConfigureNUnitTest + ); + } + private static void ConfigureNUnitTest(Verifier.Test test) { test.TestState.AdditionalReferences.Add(typeof(NUnit.Framework.TestAttribute).Assembly); diff --git a/TUnit.Assertions/Conditions/StringAssertions.cs b/TUnit.Assertions/Conditions/StringAssertions.cs index 0538f96796..744e629eb5 100644 --- a/TUnit.Assertions/Conditions/StringAssertions.cs +++ b/TUnit.Assertions/Conditions/StringAssertions.cs @@ -342,6 +342,140 @@ protected override Task CheckAsync(EvaluationMetadata m protected override string GetExpectation() => $"to end with \"{_expected}\""; } +/// +/// Asserts that a string does NOT start with the expected substring. +/// +[AssertionExtension("DoesNotStartWith")] +public class StringDoesNotStartWithAssertion : Assertion +{ + private readonly string _expected; + private StringComparison _comparison = StringComparison.Ordinal; + + public StringDoesNotStartWithAssertion( + AssertionContext context, + string expected) + : base(context) + { + _expected = expected; + } + + public StringDoesNotStartWithAssertion( + AssertionContext context, + string expected, + StringComparison comparison) + : base(context) + { + _expected = expected; + _comparison = comparison; + } + + public StringDoesNotStartWithAssertion IgnoringCase() + { + _comparison = StringComparison.OrdinalIgnoreCase; + Context.ExpressionBuilder.Append(".IgnoringCase()"); + return this; + } + + public StringDoesNotStartWithAssertion WithComparison(StringComparison comparison) + { + _comparison = comparison; + Context.ExpressionBuilder.Append($".WithComparison({comparison})"); + return this; + } + + protected override Task CheckAsync(EvaluationMetadata metadata) + { + var value = metadata.Value; + var exception = metadata.Exception; + + if (exception != null) + { + return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}")); + } + + if (value == null) + { + return Task.FromResult(AssertionResult.Failed("value was null")); + } + + if (!value.StartsWith(_expected, _comparison)) + { + return Task.FromResult(AssertionResult.Passed); + } + + return Task.FromResult(AssertionResult.Failed($"found \"{value}\"")); + } + + protected override string GetExpectation() => $"to not start with \"{_expected}\""; +} + +/// +/// Asserts that a string does NOT end with the expected substring. +/// +[AssertionExtension("DoesNotEndWith")] +public class StringDoesNotEndWithAssertion : Assertion +{ + private readonly string _expected; + private StringComparison _comparison = StringComparison.Ordinal; + + public StringDoesNotEndWithAssertion( + AssertionContext context, + string expected) + : base(context) + { + _expected = expected; + } + + public StringDoesNotEndWithAssertion( + AssertionContext context, + string expected, + StringComparison comparison) + : base(context) + { + _expected = expected; + _comparison = comparison; + } + + public StringDoesNotEndWithAssertion IgnoringCase() + { + _comparison = StringComparison.OrdinalIgnoreCase; + Context.ExpressionBuilder.Append(".IgnoringCase()"); + return this; + } + + public StringDoesNotEndWithAssertion WithComparison(StringComparison comparison) + { + _comparison = comparison; + Context.ExpressionBuilder.Append($".WithComparison({comparison})"); + return this; + } + + protected override Task CheckAsync(EvaluationMetadata metadata) + { + var value = metadata.Value; + var exception = metadata.Exception; + + if (exception != null) + { + return Task.FromResult(AssertionResult.Failed($"threw {exception.GetType().Name}")); + } + + if (value == null) + { + return Task.FromResult(AssertionResult.Failed("value was null")); + } + + if (!value.EndsWith(_expected, _comparison)) + { + return Task.FromResult(AssertionResult.Passed); + } + + return Task.FromResult(AssertionResult.Failed($"found \"{value}\"")); + } + + protected override string GetExpectation() => $"to not end with \"{_expected}\""; +} + /// /// Asserts that a string is not empty or whitespace. /// 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 3939c57d70..a450c3eac2 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 @@ -1792,6 +1792,16 @@ namespace .Conditions public . IgnoringCase() { } public . WithComparison( comparison) { } } + [.("DoesNotEndWith")] + public class StringDoesNotEndWithAssertion : . + { + public StringDoesNotEndWithAssertion(. context, string expected) { } + public StringDoesNotEndWithAssertion(. context, string expected, comparison) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + public . IgnoringCase() { } + public . WithComparison( comparison) { } + } [.("DoesNotMatch")] public class StringDoesNotMatchAssertion : . { @@ -1802,6 +1812,16 @@ namespace .Conditions public . IgnoringCase() { } public . WithOptions(. options) { } } + [.("DoesNotStartWith")] + public class StringDoesNotStartWithAssertion : . + { + public StringDoesNotStartWithAssertion(. context, string expected) { } + public StringDoesNotStartWithAssertion(. context, string expected, comparison) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + public . IgnoringCase() { } + public . WithComparison( comparison) { } + } [.("EndsWith")] public class StringEndsWithAssertion : . { @@ -4585,11 +4605,21 @@ namespace .Extensions public static . DoesNotContain(this . source, string expected, [.("expected")] string? expectedExpression = null) { } public static . DoesNotContain(this . source, string expected, comparison, [.("expected")] string? expectedExpression = null, [.("comparison")] string? comparisonExpression = null) { } } + public static class StringDoesNotEndWithAssertionExtensions + { + public static . DoesNotEndWith(this . source, string expected, [.("expected")] string? expectedExpression = null) { } + public static . DoesNotEndWith(this . source, string expected, comparison, [.("expected")] string? expectedExpression = null, [.("comparison")] string? comparisonExpression = null) { } + } public static class StringDoesNotMatchAssertionExtensions { public static . DoesNotMatch(this . source, . regex, [.("regex")] string? regexExpression = null) { } public static . DoesNotMatch(this . source, string pattern, [.("pattern")] string? patternExpression = null) { } } + public static class StringDoesNotStartWithAssertionExtensions + { + public static . DoesNotStartWith(this . source, string expected, [.("expected")] string? expectedExpression = null) { } + public static . DoesNotStartWith(this . source, string expected, comparison, [.("expected")] string? expectedExpression = null, [.("comparison")] string? comparisonExpression = null) { } + } public static class StringEndsWithAssertionExtensions { public static . EndsWith(this . source, string expected, [.("expected")] string? expectedExpression = 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 7c1db3dab2..720a72e051 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 @@ -1777,6 +1777,16 @@ namespace .Conditions public . IgnoringCase() { } public . WithComparison( comparison) { } } + [.("DoesNotEndWith")] + public class StringDoesNotEndWithAssertion : . + { + public StringDoesNotEndWithAssertion(. context, string expected) { } + public StringDoesNotEndWithAssertion(. context, string expected, comparison) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + public . IgnoringCase() { } + public . WithComparison( comparison) { } + } [.("DoesNotMatch")] public class StringDoesNotMatchAssertion : . { @@ -1787,6 +1797,16 @@ namespace .Conditions public . IgnoringCase() { } public . WithOptions(. options) { } } + [.("DoesNotStartWith")] + public class StringDoesNotStartWithAssertion : . + { + public StringDoesNotStartWithAssertion(. context, string expected) { } + public StringDoesNotStartWithAssertion(. context, string expected, comparison) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + public . IgnoringCase() { } + public . WithComparison( comparison) { } + } [.("EndsWith")] public class StringEndsWithAssertion : . { @@ -4537,11 +4557,21 @@ namespace .Extensions public static . DoesNotContain(this . source, string expected, [.("expected")] string? expectedExpression = null) { } public static . DoesNotContain(this . source, string expected, comparison, [.("expected")] string? expectedExpression = null, [.("comparison")] string? comparisonExpression = null) { } } + public static class StringDoesNotEndWithAssertionExtensions + { + public static . DoesNotEndWith(this . source, string expected, [.("expected")] string? expectedExpression = null) { } + public static . DoesNotEndWith(this . source, string expected, comparison, [.("expected")] string? expectedExpression = null, [.("comparison")] string? comparisonExpression = null) { } + } public static class StringDoesNotMatchAssertionExtensions { public static . DoesNotMatch(this . source, . regex, [.("regex")] string? regexExpression = null) { } public static . DoesNotMatch(this . source, string pattern, [.("pattern")] string? patternExpression = null) { } } + public static class StringDoesNotStartWithAssertionExtensions + { + public static . DoesNotStartWith(this . source, string expected, [.("expected")] string? expectedExpression = null) { } + public static . DoesNotStartWith(this . source, string expected, comparison, [.("expected")] string? expectedExpression = null, [.("comparison")] string? comparisonExpression = null) { } + } public static class StringEndsWithAssertionExtensions { public static . EndsWith(this . source, string expected, [.("expected")] string? expectedExpression = 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 0dfc614f00..707d2c15c2 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 @@ -1792,6 +1792,16 @@ namespace .Conditions public . IgnoringCase() { } public . WithComparison( comparison) { } } + [.("DoesNotEndWith")] + public class StringDoesNotEndWithAssertion : . + { + public StringDoesNotEndWithAssertion(. context, string expected) { } + public StringDoesNotEndWithAssertion(. context, string expected, comparison) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + public . IgnoringCase() { } + public . WithComparison( comparison) { } + } [.("DoesNotMatch")] public class StringDoesNotMatchAssertion : . { @@ -1802,6 +1812,16 @@ namespace .Conditions public . IgnoringCase() { } public . WithOptions(. options) { } } + [.("DoesNotStartWith")] + public class StringDoesNotStartWithAssertion : . + { + public StringDoesNotStartWithAssertion(. context, string expected) { } + public StringDoesNotStartWithAssertion(. context, string expected, comparison) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + public . IgnoringCase() { } + public . WithComparison( comparison) { } + } [.("EndsWith")] public class StringEndsWithAssertion : . { @@ -4585,11 +4605,21 @@ namespace .Extensions public static . DoesNotContain(this . source, string expected, [.("expected")] string? expectedExpression = null) { } public static . DoesNotContain(this . source, string expected, comparison, [.("expected")] string? expectedExpression = null, [.("comparison")] string? comparisonExpression = null) { } } + public static class StringDoesNotEndWithAssertionExtensions + { + public static . DoesNotEndWith(this . source, string expected, [.("expected")] string? expectedExpression = null) { } + public static . DoesNotEndWith(this . source, string expected, comparison, [.("expected")] string? expectedExpression = null, [.("comparison")] string? comparisonExpression = null) { } + } public static class StringDoesNotMatchAssertionExtensions { public static . DoesNotMatch(this . source, . regex, [.("regex")] string? regexExpression = null) { } public static . DoesNotMatch(this . source, string pattern, [.("pattern")] string? patternExpression = null) { } } + public static class StringDoesNotStartWithAssertionExtensions + { + public static . DoesNotStartWith(this . source, string expected, [.("expected")] string? expectedExpression = null) { } + public static . DoesNotStartWith(this . source, string expected, comparison, [.("expected")] string? expectedExpression = null, [.("comparison")] string? comparisonExpression = null) { } + } public static class StringEndsWithAssertionExtensions { public static . EndsWith(this . source, string expected, [.("expected")] string? expectedExpression = 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 cec513b140..b99883deb3 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 @@ -1580,6 +1580,16 @@ namespace .Conditions public . IgnoringCase() { } public . WithComparison( comparison) { } } + [.("DoesNotEndWith")] + public class StringDoesNotEndWithAssertion : . + { + public StringDoesNotEndWithAssertion(. context, string expected) { } + public StringDoesNotEndWithAssertion(. context, string expected, comparison) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + public . IgnoringCase() { } + public . WithComparison( comparison) { } + } [.("DoesNotMatch")] public class StringDoesNotMatchAssertion : . { @@ -1590,6 +1600,16 @@ namespace .Conditions public . IgnoringCase() { } public . WithOptions(. options) { } } + [.("DoesNotStartWith")] + public class StringDoesNotStartWithAssertion : . + { + public StringDoesNotStartWithAssertion(. context, string expected) { } + public StringDoesNotStartWithAssertion(. context, string expected, comparison) { } + protected override .<.> CheckAsync(. metadata) { } + protected override string GetExpectation() { } + public . IgnoringCase() { } + public . WithComparison( comparison) { } + } [.("EndsWith")] public class StringEndsWithAssertion : . { @@ -3964,11 +3984,21 @@ namespace .Extensions public static . DoesNotContain(this . source, string expected, [.("expected")] string? expectedExpression = null) { } public static . DoesNotContain(this . source, string expected, comparison, [.("expected")] string? expectedExpression = null, [.("comparison")] string? comparisonExpression = null) { } } + public static class StringDoesNotEndWithAssertionExtensions + { + public static . DoesNotEndWith(this . source, string expected, [.("expected")] string? expectedExpression = null) { } + public static . DoesNotEndWith(this . source, string expected, comparison, [.("expected")] string? expectedExpression = null, [.("comparison")] string? comparisonExpression = null) { } + } public static class StringDoesNotMatchAssertionExtensions { public static . DoesNotMatch(this . source, . regex, [.("regex")] string? regexExpression = null) { } public static . DoesNotMatch(this . source, string pattern, [.("pattern")] string? patternExpression = null) { } } + public static class StringDoesNotStartWithAssertionExtensions + { + public static . DoesNotStartWith(this . source, string expected, [.("expected")] string? expectedExpression = null) { } + public static . DoesNotStartWith(this . source, string expected, comparison, [.("expected")] string? expectedExpression = null, [.("comparison")] string? comparisonExpression = null) { } + } public static class StringEndsWithAssertionExtensions { public static . EndsWith(this . source, string expected, [.("expected")] string? expectedExpression = null) { }