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
101 changes: 101 additions & 0 deletions TUnit.Analyzers.Tests/ArgumentsAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,105 @@ public void Test(int a, params long[] arr)
"""
);
}

[Test]
public async Task Arguments_With_DisplayName_Property_No_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using TUnit.Core;

public class MyClass
{
[Test]
[Arguments("preferLocal", "AutoResolvePreferLocal", DisplayName = "Auto-resolve as prefer local (branch)")]
[Arguments("preferRemote", "AutoResolvePreferRemote", DisplayName = "Auto-resolve as prefer remote (org)")]
public void MergeAutoResolveIsCorrect(string conflictsArg, string expectedMetadataDirectoryName)
{
}
}
"""
);
}

[Test]
public async Task Arguments_With_Skip_Property_No_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using TUnit.Core;

public class MyClass
{
[Test]
[Arguments("value1", "value2", Skip = "Skipped for testing")]
public void MyTest(string arg1, string arg2)
{
}
}
"""
);
}

[Test]
public async Task Arguments_With_Categories_Property_No_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using TUnit.Core;

public class MyClass
{
[Test]
[Arguments("value1", "value2", Categories = new[] { "smoke", "integration" })]
public void MyTest(string arg1, string arg2)
{
}
}
"""
);
}

[Test]
public async Task Arguments_With_Multiple_Named_Properties_No_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using TUnit.Core;

public class MyClass
{
[Test]
[Arguments("value1", "value2", DisplayName = "Custom name", Categories = new[] { "smoke" })]
public void MyTest(string arg1, string arg2)
{
}
}
"""
);
}

[Test]
public async Task Generic_Arguments_With_DisplayName_Property_No_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using TUnit.Core;

public class MyClass
{
[Test]
[Arguments<string>("value", DisplayName = "Test with string")]
public void MyTest(string arg)
{
}
}
"""
);
}
}
177 changes: 176 additions & 1 deletion TUnit.Analyzers.Tests/MethodDataSourceAnalyzerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ public class MyClass(int _)
public void MyTest(int value)
{
}

public int One()
{
return 1;
Expand All @@ -723,4 +723,179 @@ public int One()
.WithLocation(0)
);
}

[Test]
public async Task Method_Data_Source_With_TestDataRow_DisplayName_No_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using TUnit.Core;
using System;
using System.Collections.Generic;

public class MyClass
{
[MethodDataSource(nameof(GetData))]
[Test]
public void MyTest(string arg1, string arg2)
{
}

public static IEnumerable<TestDataRow<(string, string)>> GetData()
{
yield return new(("preferLocal", "AutoResolvePreferLocal"), DisplayName: "Auto-resolve as prefer local");
yield return new(("preferRemote", "AutoResolvePreferRemote"), DisplayName: "Auto-resolve as prefer remote");
}
}
"""
);
}

[Test]
public async Task Method_Data_Source_With_TestDataRow_Skip_No_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using TUnit.Core;
using System;
using System.Collections.Generic;

public class MyClass
{
[MethodDataSource(nameof(GetData))]
[Test]
public void MyTest(string arg1, string arg2)
{
}

public static IEnumerable<TestDataRow<(string, string)>> GetData()
{
yield return new(("test1", "value1"), Skip: "Temporarily skipped");
yield return new(("test2", "value2"));
}
}
"""
);
}

[Test]
public async Task Method_Data_Source_With_TestDataRow_Categories_No_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using TUnit.Core;
using System;
using System.Collections.Generic;

public class MyClass
{
[MethodDataSource(nameof(GetData))]
[Test]
public void MyTest(string arg)
{
}

public static IEnumerable<TestDataRow<string>> GetData()
{
yield return new("value1", Categories: new[] { "smoke", "fast" });
yield return new("value2", DisplayName: "Custom name", Categories: new[] { "integration" });
}
}
"""
);
}

[Test]
public async Task Method_Data_Source_With_TestDataRow_All_Metadata_No_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using TUnit.Core;
using System;
using System.Collections.Generic;

public class MyClass
{
[MethodDataSource(nameof(GetData))]
[Test]
public void MyTest(string arg1, string arg2)
{
}

public static IEnumerable<TestDataRow<(string, string)>> GetData()
{
yield return new(("test1", "value1"), DisplayName: "Test case 1", Skip: null, Categories: new[] { "smoke" });
yield return new(("test2", "value2"), DisplayName: "Test case 2", Categories: new[] { "integration" });
}
}
"""
);
}

[Test]
public async Task Method_Data_Source_With_Func_TestDataRow_No_Error()
{
await Verifier
.VerifyAnalyzerAsync(
"""
using TUnit.Core;
using System;
using System.Collections.Generic;

public class MyClass
{
[MethodDataSource(nameof(GetData))]
[Test]
public void MyTest(string arg)
{
}

public static IEnumerable<Func<TestDataRow<string>>> GetData()
{
yield return () => new("value1", DisplayName: "First test");
yield return () => new("value2", DisplayName: "Second test");
}
}
"""
);
}

[Test]
public async Task Method_Data_Source_With_Func_TestDataRow_ReferenceType_No_Warning()
{
// Func<TestDataRow<T>> with reference type should NOT trigger TUnit0046
// because Func provides the isolation
await Verifier
.VerifyAnalyzerAsync(
"""
using TUnit.Core;
using System;
using System.Collections.Generic;

public class MyData(string value)
{
public string Value { get; } = value;
}

public class MyClass
{
[MethodDataSource(nameof(GetData))]
[Test]
public void MyTest(MyData data)
{
}

public static IEnumerable<Func<TestDataRow<MyData>>> GetData()
{
yield return () => new(new MyData("test1"), DisplayName: "First");
yield return () => new(new MyData("test2"), DisplayName: "Second");
}
}
"""
);
}
}
10 changes: 10 additions & 0 deletions TUnit.Analyzers/TestDataAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,16 @@ private ImmutableArray<ITypeSymbol> UnwrapTypes(SymbolAnalysisContext context,
type = genericType.TypeArguments[0];
}

// Check for TestDataRow<T> wrapper - unwrap to get the inner data type
// This must come after Func<T> unwrapping so that Func<TestDataRow<T>> → TestDataRow<T> → T
var testDataRowTypeSymbol = context.Compilation.GetTypeByMetadataName("TUnit.Core.TestDataRow`1");
if (testDataRowTypeSymbol != null
&& type is INamedTypeSymbol { IsGenericType: true } testDataRowType
&& SymbolEqualityComparer.Default.Equals(testDataRowType.OriginalDefinition, testDataRowTypeSymbol))
{
type = testDataRowType.TypeArguments[0];
}

if (type is INamedTypeSymbol namedType && namedType.IsTupleType)
{
var tupleType = namedType;
Expand Down
Loading