From 466c3534c050fedeb04307e255a986568ea60d92 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 Aug 2025 19:25:02 +0000 Subject: [PATCH 1/4] Initial plan From 83080fd8c4e5e69cf5769e6cd0cec8fb4b22ddd3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 Aug 2025 19:40:49 +0000 Subject: [PATCH 2/4] Update DisplayNameAttribute to support class-level usage Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com> --- .../TestMetadata/DisplayNameAttribute.cs | 2 +- .../ClassDisplayNameAttributeTests.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 TUnit.TestProject/ClassDisplayNameAttributeTests.cs diff --git a/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs b/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs index fb4b4cccff4..101e314328a 100644 --- a/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs +++ b/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs @@ -28,7 +28,7 @@ namespace TUnit.Core; /// /// The display name template. Can include parameter placeholders in the format of "$parameterName". /// -[AttributeUsage(AttributeTargets.Method, Inherited = false)] +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)] public sealed class DisplayNameAttribute(string displayName) : DisplayNameFormatterAttribute, IScopedAttribute { /// diff --git a/TUnit.TestProject/ClassDisplayNameAttributeTests.cs b/TUnit.TestProject/ClassDisplayNameAttributeTests.cs new file mode 100644 index 00000000000..007e73e0c6f --- /dev/null +++ b/TUnit.TestProject/ClassDisplayNameAttributeTests.cs @@ -0,0 +1,16 @@ +using TUnit.TestProject.Attributes; + +namespace TUnit.TestProject; + +[EngineTest(ExpectedResult.Pass)] +[DisplayName("Custom Class Display Name")] +public class ClassDisplayNameAttributeTests +{ + [Test] + public async Task Test() + { + // This test should inherit the class display name as a prefix or part of the test name + await Assert.That(TestContext.Current!.GetDisplayName()) + .DoesNotContain("ClassDisplayNameAttributeTests"); + } +} \ No newline at end of file From 59052c2c6b27106ea5a0ecea79ab979890da0619 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 Aug 2025 19:52:31 +0000 Subject: [PATCH 3/4] Enhance DisplayNameAttribute to support class-level parameters and improve documentation Co-authored-by: thomhurst <30480171+thomhurst@users.noreply.github.com> --- .../TestMetadata/DisplayNameAttribute.cs | 36 +++++++++++++++---- .../ClassDisplayNameAttributeTests.cs | 15 ++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs b/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs index 101e314328a..f9cb349aee1 100644 --- a/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs +++ b/TUnit.Core/Attributes/TestMetadata/DisplayNameAttribute.cs @@ -5,28 +5,35 @@ namespace TUnit.Core; /// -/// Attribute that allows specifying a custom display name for a test method. +/// Attribute that allows specifying a custom display name for a test method or test class. /// /// /// -/// This attribute can be applied to test methods to provide more descriptive names than the default method name. +/// This attribute can be applied to test methods or test classes to provide more descriptive names than the default method or class name. /// /// /// The display name can include parameter placeholders in the format of "$parameterName" which will be -/// replaced with the actual parameter values during test execution. For example: +/// replaced with the actual parameter values during test execution. For test methods, method parameters +/// will be used for substitution. For test classes, constructor parameters will be used for substitution. For example: /// /// [Test] /// [Arguments("John", 25)] /// [DisplayName("User $name is $age years old")] /// public void TestUser(string name, int age) { ... } +/// +/// [Arguments("TestData")] +/// [DisplayName("Class with data: $data")] +/// public class MyTestClass(string data) { ... } /// /// /// -/// When this test runs, the display name would appear as "User John is 25 years old". +/// When these tests run, the display names would appear as "User John is 25 years old" and +/// "Class with data: TestData" respectively. /// /// /// /// The display name template. Can include parameter placeholders in the format of "$parameterName". +/// For methods, method parameter names can be referenced. For classes, constructor parameter names can be referenced. /// [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)] public sealed class DisplayNameAttribute(string displayName) : DisplayNameFormatterAttribute, IScopedAttribute @@ -38,17 +45,34 @@ protected override string FormatDisplayName(DiscoveredTestContext context) var mutableDisplayName = displayName; - var parameters = testDetails + // Try to substitute method parameters first + var methodParameters = testDetails .MethodMetadata .Parameters .Zip(testDetails.TestMethodArguments, (parameterInfo, testArgument) => (ParameterInfo: parameterInfo, TestArgument: testArgument)); - foreach (var parameter in parameters) + foreach (var parameter in methodParameters) { mutableDisplayName = mutableDisplayName.Replace($"${parameter.ParameterInfo.Name}", ArgumentFormatter.Format(parameter.TestArgument, context.ArgumentDisplayFormatters)); } + // If there are still placeholders and we have class parameters, try to substitute them + if (mutableDisplayName.Contains('$') && testDetails.TestClassArguments.Length > 0) + { + var classParameters = testDetails + .MethodMetadata + .Class + .Parameters + .Zip(testDetails.TestClassArguments, (parameterInfo, testArgument) => (ParameterInfo: parameterInfo, TestArgument: testArgument)); + + foreach (var parameter in classParameters) + { + mutableDisplayName = mutableDisplayName.Replace($"${parameter.ParameterInfo.Name}", + ArgumentFormatter.Format(parameter.TestArgument, context.ArgumentDisplayFormatters)); + } + } + return mutableDisplayName; } } diff --git a/TUnit.TestProject/ClassDisplayNameAttributeTests.cs b/TUnit.TestProject/ClassDisplayNameAttributeTests.cs index 007e73e0c6f..2641c0d2812 100644 --- a/TUnit.TestProject/ClassDisplayNameAttributeTests.cs +++ b/TUnit.TestProject/ClassDisplayNameAttributeTests.cs @@ -13,4 +13,19 @@ public async Task Test() await Assert.That(TestContext.Current!.GetDisplayName()) .DoesNotContain("ClassDisplayNameAttributeTests"); } +} + +[EngineTest(ExpectedResult.Pass)] +[Arguments("TestValue")] +[DisplayName("Class with parameter: $value")] +public class ClassDisplayNameWithParametersTests(string value) +{ + [Test] + public async Task Test() + { + // This test should show the class display name with parameter substitution + var displayName = TestContext.Current!.GetDisplayName(); + await Assert.That(displayName) + .Contains("TestValue"); + } } \ No newline at end of file From 18bb0e93084c6980dfae4d6449d9b8c02c33176e Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Sun, 3 Aug 2025 22:41:41 +0100 Subject: [PATCH 4/4] Update public API snapshots after rebasing on main --- ...Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt | 2 +- ...Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt | 2 +- .../Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt | 2 +- global.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt index bc50475c2f8..8e495d0073f 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet8_0.verified.txt @@ -577,7 +577,7 @@ namespace public DiscoveryResult() { } public static .DiscoveryResult Empty { get; } } - [(.Method, Inherited=false)] + [(.Class | .Method, Inherited=false)] public sealed class DisplayNameAttribute : .DisplayNameFormatterAttribute, .IScopedAttribute, .IScopedAttribute<.DisplayNameAttribute> { public DisplayNameAttribute(string displayName) { } diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt index 31044dbadbb..02039171a27 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.DotNet9_0.verified.txt @@ -577,7 +577,7 @@ namespace public DiscoveryResult() { } public static .DiscoveryResult Empty { get; } } - [(.Method, Inherited=false)] + [(.Class | .Method, Inherited=false)] public sealed class DisplayNameAttribute : .DisplayNameFormatterAttribute, .IScopedAttribute, .IScopedAttribute<.DisplayNameAttribute> { public DisplayNameAttribute(string displayName) { } diff --git a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt index 33f833323fc..0dce58cb703 100644 --- a/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt +++ b/TUnit.PublicAPI/Tests.Core_Library_Has_No_API_Changes.Net4_7.verified.txt @@ -531,7 +531,7 @@ namespace public DiscoveryResult() { } public static .DiscoveryResult Empty { get; } } - [(.Method, Inherited=false)] + [(.Class | .Method, Inherited=false)] public sealed class DisplayNameAttribute : .DisplayNameFormatterAttribute, .IScopedAttribute, .IScopedAttribute<.DisplayNameAttribute> { public DisplayNameAttribute(string displayName) { } diff --git a/global.json b/global.json index 39667c87d8a..3f28e42f5e1 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.303", + "version": "9.0.302", "rollForward": "latestFeature" } }