Skip to content

Commit fd7fa69

Browse files
committed
Add unit tests for forwarded [RelayCommand] attributes
1 parent 476a163 commit fd7fa69

File tree

4 files changed

+296
-4
lines changed

4 files changed

+296
-4
lines changed

tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,81 @@ public object? A
193193
VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
194194
}
195195

196+
[TestMethod]
197+
public void RelayCommandMethodWithForwardedAttributesWithNumberLiterals_PreservesType()
198+
{
199+
string source = """
200+
using CommunityToolkit.Mvvm.Input;
201+
202+
#nullable enable
203+
204+
namespace MyApp;
205+
206+
partial class MyViewModel
207+
{
208+
const double MyDouble = 3.14;
209+
const float MyFloat = 3.14f;
210+
211+
[RelayCommand]
212+
[field: DefaultValue(0.0)]
213+
[field: DefaultValue(1.24)]
214+
[field: DefaultValue(0.0f)]
215+
[field: DefaultValue(0.0f)]
216+
[field: DefaultValue(MyDouble)]
217+
[field: DefaultValue(MyFloat)]
218+
[property: DefaultValue(0.0)]
219+
[property: DefaultValue(1.24)]
220+
[property: DefaultValue(0.0f)]
221+
[property: DefaultValue(0.0f)]
222+
[property: DefaultValue(MyDouble)]
223+
[property: DefaultValue(MyFloat)]
224+
private void Test()
225+
{
226+
}
227+
}
228+
229+
public class DefaultValueAttribute : Attribute
230+
{
231+
public DefaultValueAttribute(object value)
232+
{
233+
}
234+
}
235+
""";
236+
237+
string result = """
238+
// <auto-generated/>
239+
#pragma warning disable
240+
#nullable enable
241+
namespace MyApp
242+
{
243+
partial class MyViewModel
244+
{
245+
/// <summary>The backing field for <see cref="TestCommand"/>.</summary>
246+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", "8.1.0.0")]
247+
[global::MyApp.DefaultValueAttribute(0D)]
248+
[global::MyApp.DefaultValueAttribute(1.24D)]
249+
[global::MyApp.DefaultValueAttribute(0F)]
250+
[global::MyApp.DefaultValueAttribute(0F)]
251+
[global::MyApp.DefaultValueAttribute(3.14D)]
252+
[global::MyApp.DefaultValueAttribute(3.14F)]
253+
private global::CommunityToolkit.Mvvm.Input.RelayCommand? testCommand;
254+
/// <summary>Gets an <see cref="global::CommunityToolkit.Mvvm.Input.IRelayCommand"/> instance wrapping <see cref="Test"/>.</summary>
255+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.RelayCommandGenerator", "8.1.0.0")]
256+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
257+
[global::MyApp.DefaultValueAttribute(0D)]
258+
[global::MyApp.DefaultValueAttribute(1.24D)]
259+
[global::MyApp.DefaultValueAttribute(0F)]
260+
[global::MyApp.DefaultValueAttribute(0F)]
261+
[global::MyApp.DefaultValueAttribute(3.14D)]
262+
[global::MyApp.DefaultValueAttribute(3.14F)]
263+
public global::CommunityToolkit.Mvvm.Input.IRelayCommand TestCommand => testCommand ??= new global::CommunityToolkit.Mvvm.Input.RelayCommand(new global::System.Action(Test));
264+
}
265+
}
266+
""";
267+
268+
VerifyGenerateSources(source, new[] { new RelayCommandGenerator() }, ("MyApp.MyViewModel.Test.g.cs", result));
269+
}
270+
196271
[TestMethod]
197272
public void ObservablePropertyWithinGenericAndNestedTypes()
198273
{

tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsDiagnostics.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1672,6 +1672,59 @@ public partial class MyViewModel : ObservableObject
16721672
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0035");
16731673
}
16741674

1675+
[TestMethod]
1676+
public void InvalidPropertyTargetedAttributeOnRelayCommandMethod_MissingUsingDirective()
1677+
{
1678+
string source = """
1679+
using System;
1680+
using CommunityToolkit.Mvvm.Input;
1681+
1682+
namespace MyApp
1683+
{
1684+
public partial class MyViewModel
1685+
{
1686+
[RelayCommand]
1687+
[property: MyTest]
1688+
private void Test()
1689+
{
1690+
}
1691+
}
1692+
}
1693+
1694+
namespace MyAttributes
1695+
{
1696+
[AttributeUsage(AttributeTargets.Property)]
1697+
public class MyTestAttribute : Attribute
1698+
{
1699+
}
1700+
}
1701+
""";
1702+
1703+
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0036");
1704+
}
1705+
1706+
[TestMethod]
1707+
public void InvalidPropertyTargetedAttributeOnRelayCommandMethod_TypoInAttributeName()
1708+
{
1709+
string source = """
1710+
using CommunityToolkit.Mvvm.Input;
1711+
1712+
namespace MyApp
1713+
{
1714+
public partial class MyViewModel
1715+
{
1716+
[RelayCommand]
1717+
[property: Fbuifbweif]
1718+
private void Test()
1719+
{
1720+
}
1721+
}
1722+
}
1723+
""";
1724+
1725+
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0036");
1726+
}
1727+
16751728
/// <summary>
16761729
/// Verifies the diagnostic errors for a given analyzer, and that all available source generators can run successfully with the input source (including subsequent compilation).
16771730
/// </summary>

tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservablePropertyAttribute.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1611,13 +1611,13 @@ public partial class MyViewModelWithExplicitPropertyAttributes : ObservableValid
16111611
private int someComplexRandomAttribute;
16121612
}
16131613

1614-
[AttributeUsage(AttributeTargets.Property)]
1615-
private sealed class TestAttribute : Attribute
1614+
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
1615+
public sealed class TestAttribute : Attribute
16161616
{
16171617
}
16181618

1619-
[AttributeUsage(AttributeTargets.Property)]
1620-
private sealed class PropertyInfoAttribute : Attribute
1619+
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
1620+
public sealed class PropertyInfoAttribute : Attribute
16211621
{
16221622
public PropertyInfoAttribute(object? o, Type t, bool flag, double d, string[] names, object[] objects)
16231623
{

tests/CommunityToolkit.Mvvm.UnitTests/Test_RelayCommandAttribute.cs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44

55
using System;
66
using System.Collections.Generic;
7+
using System.ComponentModel.DataAnnotations;
78
using System.Linq;
89
using System.Reflection;
10+
using System.Text.Json.Serialization;
911
using System.Threading;
1012
using System.Threading.Tasks;
13+
using System.Xml.Serialization;
1114
using CommunityToolkit.Mvvm.ComponentModel;
1215
using CommunityToolkit.Mvvm.Input;
1316
using Microsoft.VisualStudio.TestTools.UnitTesting;
@@ -565,6 +568,97 @@ public void Test_RelayCommandAttribute_CanExecuteWithNullabilityAnnotations()
565568
Assert.IsTrue(model.DoSomething3Command.CanExecute((0, "Hello")));
566569
}
567570

571+
[TestMethod]
572+
public void Test_RelayCommandAttribute_WithExplicitAttributesForFieldAndProperty()
573+
{
574+
FieldInfo fooField = typeof(MyViewModelWithExplicitFieldAndPropertyAttributes).GetField("fooCommand", BindingFlags.Instance | BindingFlags.NonPublic)!;
575+
576+
Assert.IsNotNull(fooField.GetCustomAttribute<RequiredAttribute>());
577+
Assert.IsNotNull(fooField.GetCustomAttribute<MinLengthAttribute>());
578+
Assert.AreEqual(fooField.GetCustomAttribute<MinLengthAttribute>()!.Length, 1);
579+
Assert.IsNotNull(fooField.GetCustomAttribute<MaxLengthAttribute>());
580+
Assert.AreEqual(fooField.GetCustomAttribute<MaxLengthAttribute>()!.Length, 100);
581+
582+
PropertyInfo fooProperty = typeof(MyViewModelWithExplicitFieldAndPropertyAttributes).GetProperty("FooCommand")!;
583+
584+
Assert.IsNotNull(fooProperty.GetCustomAttribute<RequiredAttribute>());
585+
Assert.IsNotNull(fooProperty.GetCustomAttribute<MinLengthAttribute>());
586+
Assert.AreEqual(fooProperty.GetCustomAttribute<MinLengthAttribute>()!.Length, 1);
587+
Assert.IsNotNull(fooProperty.GetCustomAttribute<MaxLengthAttribute>());
588+
Assert.AreEqual(fooProperty.GetCustomAttribute<MaxLengthAttribute>()!.Length, 100);
589+
590+
PropertyInfo barProperty = typeof(MyViewModelWithExplicitFieldAndPropertyAttributes).GetProperty("BarCommand")!;
591+
592+
Assert.IsNotNull(barProperty.GetCustomAttribute<JsonPropertyNameAttribute>());
593+
Assert.AreEqual(barProperty.GetCustomAttribute<JsonPropertyNameAttribute>()!.Name, "bar");
594+
Assert.IsNotNull(barProperty.GetCustomAttribute<XmlIgnoreAttribute>());
595+
596+
PropertyInfo bazProperty = typeof(MyViewModelWithExplicitFieldAndPropertyAttributes).GetProperty("BazCommand")!;
597+
598+
Assert.IsNotNull(bazProperty.GetCustomAttribute<Test_ObservablePropertyAttribute.TestAttribute>());
599+
600+
static void ValidateTestAttribute(TestValidationAttribute testAttribute)
601+
{
602+
Assert.IsNotNull(testAttribute);
603+
Assert.IsNull(testAttribute.O);
604+
Assert.AreEqual(testAttribute.T, typeof(MyViewModelWithExplicitFieldAndPropertyAttributes));
605+
Assert.AreEqual(testAttribute.Flag, true);
606+
Assert.AreEqual(testAttribute.D, 6.28);
607+
CollectionAssert.AreEqual(testAttribute.Names, new[] { "Bob", "Ross" });
608+
609+
object[]? nestedArray = (object[]?)testAttribute.NestedArray;
610+
611+
Assert.IsNotNull(nestedArray);
612+
Assert.AreEqual(nestedArray!.Length, 3);
613+
Assert.AreEqual(nestedArray[0], 1);
614+
Assert.AreEqual(nestedArray[1], "Hello");
615+
Assert.IsTrue(nestedArray[2] is int[]);
616+
CollectionAssert.AreEqual((int[])nestedArray[2], new[] { 2, 3, 4 });
617+
618+
Assert.AreEqual(testAttribute.Animal, Test_ObservablePropertyAttribute.Animal.Llama);
619+
}
620+
621+
FieldInfo fooBarField = typeof(MyViewModelWithExplicitFieldAndPropertyAttributes).GetField("fooBarCommand", BindingFlags.Instance | BindingFlags.NonPublic)!;
622+
623+
ValidateTestAttribute(fooBarField.GetCustomAttribute<TestValidationAttribute>()!);
624+
625+
PropertyInfo fooBarProperty = typeof(MyViewModelWithExplicitFieldAndPropertyAttributes).GetProperty("FooBarCommand")!;
626+
627+
ValidateTestAttribute(fooBarProperty.GetCustomAttribute<TestValidationAttribute>()!);
628+
629+
FieldInfo barBazField = typeof(MyViewModelWithExplicitFieldAndPropertyAttributes).GetField("barBazCommand", BindingFlags.Instance | BindingFlags.NonPublic)!;
630+
631+
Assert.IsNotNull(barBazField.GetCustomAttribute<Test_ObservablePropertyAttribute.TestAttribute>());
632+
633+
PropertyInfo barBazCommand = typeof(MyViewModelWithExplicitFieldAndPropertyAttributes).GetProperty("BarBazCommand")!;
634+
635+
Assert.IsNotNull(barBazCommand.GetCustomAttribute<Test_ObservablePropertyAttribute.TestAttribute>());
636+
637+
Test_ObservablePropertyAttribute.PropertyInfoAttribute testAttribute2 = barBazCommand.GetCustomAttribute<Test_ObservablePropertyAttribute.PropertyInfoAttribute>()!;
638+
639+
Assert.IsNotNull(testAttribute2);
640+
Assert.IsNull(testAttribute2.O);
641+
Assert.AreEqual(testAttribute2.T, typeof(MyViewModelWithExplicitFieldAndPropertyAttributes));
642+
Assert.AreEqual(testAttribute2.Flag, true);
643+
Assert.AreEqual(testAttribute2.D, 6.28);
644+
Assert.IsNotNull(testAttribute2.Objects);
645+
Assert.IsTrue(testAttribute2.Objects is object[]);
646+
Assert.AreEqual(((object[])testAttribute2.Objects).Length, 1);
647+
Assert.AreEqual(((object[])testAttribute2.Objects)[0], "Test");
648+
CollectionAssert.AreEqual(testAttribute2.Names, new[] { "Bob", "Ross" });
649+
650+
object[]? nestedArray2 = (object[]?)testAttribute2.NestedArray;
651+
652+
Assert.IsNotNull(nestedArray2);
653+
Assert.AreEqual(nestedArray2!.Length, 4);
654+
Assert.AreEqual(nestedArray2[0], 1);
655+
Assert.AreEqual(nestedArray2[1], "Hello");
656+
Assert.AreEqual(nestedArray2[2], 42);
657+
Assert.IsNull(nestedArray2[3]);
658+
659+
Assert.AreEqual(testAttribute2.Animal, (Test_ObservablePropertyAttribute.Animal)67);
660+
}
661+
568662
#region Region
569663
public class Region
570664
{
@@ -1038,4 +1132,74 @@ private void DoSomething3((int A, string? B) parameter)
10381132
{
10391133
}
10401134
}
1135+
1136+
public partial class MyViewModelWithExplicitFieldAndPropertyAttributes
1137+
{
1138+
[RelayCommand]
1139+
[field: Required]
1140+
[field: MinLength(1)]
1141+
[field: MaxLength(100)]
1142+
[property: Required]
1143+
[property: MinLength(1)]
1144+
[property: MaxLength(100)]
1145+
private void Foo()
1146+
{
1147+
}
1148+
1149+
[RelayCommand]
1150+
[property: JsonPropertyName("bar")]
1151+
[property: XmlIgnore]
1152+
private void Bar()
1153+
{
1154+
}
1155+
1156+
[RelayCommand]
1157+
[property: Test_ObservablePropertyAttribute.Test]
1158+
private async Task BazAsync()
1159+
{
1160+
await Task.Yield();
1161+
}
1162+
1163+
[RelayCommand]
1164+
[field: TestValidation(null, typeof(MyViewModelWithExplicitFieldAndPropertyAttributes), true, 6.28, new[] { "Bob", "Ross" }, NestedArray = new object[] { 1, "Hello", new int[] { 2, 3, 4 } }, Animal = Test_ObservablePropertyAttribute.Animal.Llama)]
1165+
[property: TestValidation(null, typeof(MyViewModelWithExplicitFieldAndPropertyAttributes), true, 6.28, new[] { "Bob", "Ross" }, NestedArray = new object[] { 1, "Hello", new int[] { 2, 3, 4 } }, Animal = Test_ObservablePropertyAttribute.Animal.Llama)]
1166+
private void FooBar()
1167+
{
1168+
}
1169+
1170+
[RelayCommand]
1171+
[field: Test_ObservablePropertyAttribute.Test]
1172+
[property: Test_ObservablePropertyAttribute.Test]
1173+
[property: Test_ObservablePropertyAttribute.PropertyInfo(null, typeof(MyViewModelWithExplicitFieldAndPropertyAttributes), true, 6.28, new[] { "Bob", "Ross" }, new object[] { "Test" }, NestedArray = new object[] { 1, "Hello", 42, null }, Animal = (Test_ObservablePropertyAttribute.Animal)67)]
1174+
private void BarBaz()
1175+
{
1176+
}
1177+
}
1178+
1179+
// Copy of the attribute from Test_ObservablePropertyAttribute, to test nested types
1180+
private sealed class TestValidationAttribute : ValidationAttribute
1181+
{
1182+
public TestValidationAttribute(object? o, Type t, bool flag, double d, string[] names)
1183+
{
1184+
O = o;
1185+
T = t;
1186+
Flag = flag;
1187+
D = d;
1188+
Names = names;
1189+
}
1190+
1191+
public object? O { get; }
1192+
1193+
public Type T { get; }
1194+
1195+
public bool Flag { get; }
1196+
1197+
public double D { get; }
1198+
1199+
public string[] Names { get; }
1200+
1201+
public object? NestedArray { get; set; }
1202+
1203+
public Test_ObservablePropertyAttribute.Animal Animal { get; set; }
1204+
}
10411205
}

0 commit comments

Comments
 (0)