Files
Sergio Pedri 12ae36b594 Refactor unit tests to use new assertion helpers
Replaces usages of Assert.AreEqual, Assert.IsTrue, and similar assertions with new helper methods such as Assert.HasCount, Assert.IsEmpty, Assert.IsNull, and Assert.IsGreaterThanOrEqualTo across multiple test files. This improves test readability and consistency with updated assertion APIs.
2025-11-09 18:34:09 -08:00

2599 lines
83 KiB
C#

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.SourceGenerators.UnitTests.Helpers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace CommunityToolkit.Mvvm.SourceGenerators.UnitTests;
[TestClass]
public partial class Test_SourceGeneratorsDiagnostics
{
[TestMethod]
public void DuplicateINotifyPropertyChangedInterfaceForINotifyPropertyChangedAttributeError_Explicit()
{
string source = """
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[INotifyPropertyChanged]
public partial class SampleViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
}
}
""";
VerifyGeneratedDiagnostics<INotifyPropertyChangedGenerator>(source, "MVVMTK0001");
}
[TestMethod]
public void DuplicateINotifyPropertyChangedInterfaceForINotifyPropertyChangedAttributeError_Inherited()
{
string source = """
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace CommunityToolkit.Mvvm.ComponentModel
{
public abstract class ObservableObject : INotifyPropertyChanged, INotifyPropertyChanging
{
public event PropertyChangedEventHandler? PropertyChanged;
public event PropertyChangingEventHandler? PropertyChanging;
}
}
namespace MyApp
{
[INotifyPropertyChanged]
public partial class SampleViewModel : ObservableObject
{
}
}
""";
VerifyGeneratedDiagnostics<INotifyPropertyChangedGenerator>(source, "MVVMTK0001");
}
[TestMethod]
public void DuplicateINotifyPropertyChangedInterfaceForObservableObjectAttributeError_Explicit()
{
string source = """
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[ObservableObject]
public partial class SampleViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
}
}
""";
VerifyGeneratedDiagnostics<ObservableObjectGenerator>(source, "MVVMTK0002");
}
[TestMethod]
public void DuplicateINotifyPropertyChangedInterfaceForObservableObjectAttributeError_Inherited()
{
string source = """
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace CommunityToolkit.Mvvm.ComponentModel
{
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
}
}
namespace MyApp
{
[ObservableObject]
public partial class SampleViewModel : ObservableObject
{
}
}
""";
VerifyGeneratedDiagnostics<ObservableObjectGenerator>(source, "MVVMTK0002");
}
[TestMethod]
public void DuplicateINotifyPropertyChangingInterfaceForObservableObjectAttributeError_Explicit()
{
string source = """
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[ObservableObject]
public partial class SampleViewModel : INotifyPropertyChanging
{
public event PropertyChangingEventHandler? PropertyChanging;
}
}
""";
VerifyGeneratedDiagnostics<ObservableObjectGenerator>(source, "MVVMTK0003");
}
[TestMethod]
public void DuplicateINotifyPropertyChangingInterfaceForObservableObjectAttributeError_Inherited()
{
string source = """
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public abstract class MyBaseViewModel : INotifyPropertyChanging
{
public event PropertyChangingEventHandler? PropertyChanging;
}
[ObservableObject]
public partial class SampleViewModel : MyBaseViewModel
{
}
}
""";
VerifyGeneratedDiagnostics<ObservableObjectGenerator>(source, "MVVMTK0003");
}
[TestMethod]
public void DuplicateObservableRecipientError()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace CommunityToolkit.Mvvm.ComponentModel
{
public abstract class ObservableRecipient : ObservableObject
{
}
}
namespace MyApp
{
[ObservableRecipient]
public partial class SampleViewModel : ObservableRecipient
{
}
}
""";
VerifyGeneratedDiagnostics<ObservableRecipientGenerator>(source, "MVVMTK0004");
}
[TestMethod]
public void MissingBaseObservableObjectFunctionalityError()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[ObservableRecipient]
public partial class SampleViewModel
{
}
}
""";
VerifyGeneratedDiagnostics<ObservableRecipientGenerator>(source, "MVVMTK0005");
}
[TestMethod]
public void MissingObservableValidatorInheritanceForValidationAttributeError()
{
string source = """
using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[INotifyPropertyChanged]
public partial class SampleViewModel
{
[ObservableProperty]
[Required]
private string name;
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0006");
}
[TestMethod]
public void InvalidRelayCommandMethodSignatureError()
{
string source = """
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel
{
[RelayCommand]
private string GreetUser() => "Hello world!";
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0007");
}
[TestMethod]
public async Task UnsupportedCSharpLanguageVersion_FromINotifyPropertyChangedGenerator()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[INotifyPropertyChanged]
public partial class {|MVVMTK0008:SampleViewModel|}
{
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<UnsupportedCSharpLanguageVersionAnalyzer>(source, LanguageVersion.CSharp7_3);
}
[TestMethod]
public async Task UnsupportedCSharpLanguageVersion_FromObservableObjectGenerator()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[ObservableObject]
public partial class {|MVVMTK0008:SampleViewModel|}
{
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<UnsupportedCSharpLanguageVersionAnalyzer>(source, LanguageVersion.CSharp7_3);
}
[TestMethod]
public async Task UnsupportedCSharpLanguageVersion_FromObservablePropertyGenerator()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[INotifyPropertyChanged]
public partial class {|MVVMTK0008:SampleViewModel|}
{
[ObservableProperty]
private string {|MVVMTK0008:name|};
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<UnsupportedCSharpLanguageVersionAnalyzer>(source, LanguageVersion.CSharp7_3);
}
[TestMethod]
public async Task UnsupportedCSharpLanguageVersion_FromObservablePropertyGenerator_MultipleAttributes()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[ObservableObject]
public partial class {|MVVMTK0008:SampleViewModel|}
{
[ObservableProperty]
string {|MVVMTK0008:name|};
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(Bar))]
int {|MVVMTK0008:number|};
string Bar { get; set; }
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<UnsupportedCSharpLanguageVersionAnalyzer>(source, LanguageVersion.CSharp7_3);
}
[TestMethod]
public async Task UnsupportedCSharpLanguageVersion_FromObservableValidatorValidateAllPropertiesGenerator()
{
string source = """
using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class SampleViewModel : ObservableValidator
{
[Required]
public string Name { get; set; }
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<UnsupportedCSharpLanguageVersionAnalyzer>(source, LanguageVersion.CSharp7_3);
}
[TestMethod]
public async Task UnsupportedCSharpLanguageVersion_FromRelayCommandGenerator()
{
string source = """
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel
{
[RelayCommand]
private void {|MVVMTK0008:GreetUser|}(object value)
{
}
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<UnsupportedCSharpLanguageVersionAnalyzer>(source, LanguageVersion.CSharp7_3);
}
[TestMethod]
public async Task UnsupportedCSharpLanguageVersion_FromIMessengerRegisterAllGenerator()
{
string source = """
using CommunityToolkit.Mvvm.Messaging;
namespace MyApp
{
public class MyMessage
{
}
public partial class SampleViewModel : IRecipient<MyMessage>
{
public void Receive(MyMessage message)
{
}
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<UnsupportedCSharpLanguageVersionAnalyzer>(source, LanguageVersion.CSharp7_3);
}
[TestMethod]
public void InvalidCanExecuteMemberName()
{
string source = """
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel
{
private bool Foo => true;
[RelayCommand(CanExecute = "Bar")]
private void GreetUser()
{
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0009");
}
[TestMethod]
public void MultipleCanExecuteMemberNameMatches()
{
string source = """
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel
{
private bool Foo => true;
private bool Foo() => true;
[RelayCommand(CanExecute = nameof(Foo))]
private void GreetUser()
{
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0010");
}
[TestMethod]
public void InvalidCanExecuteMember_NonReadableProperty()
{
string source = """
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel
{
private bool Foo { set { } }
[RelayCommand(CanExecute = nameof(Foo))]
private void GreetUser()
{
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0011");
}
[TestMethod]
public void InvalidCanExecuteMember_PropertyWithInvalidType()
{
string source = """
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel
{
private string Foo => "Hi!";
[RelayCommand(CanExecute = nameof(Foo))]
private void GreetUser()
{
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0011");
}
[TestMethod]
public void InvalidCanExecuteMember_MethodWithInvalidType()
{
string source = """
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel
{
private string Foo() => "Hi!";
[RelayCommand(CanExecute = nameof(Foo))]
private void GreetUser()
{
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0011");
}
[TestMethod]
public void InvalidCanExecuteMember_MethodWithIncompatibleInputType_MissingInput()
{
string source = """
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel
{
private bool Foo(string name) => true;
[RelayCommand(CanExecute = nameof(Foo))]
private void GreetUser()
{
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0011");
}
[TestMethod]
public void InvalidCanExecuteMember_MethodWithIncompatibleInputType_NonMatchingInputType()
{
string source = """
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel
{
private bool Foo(int age) => true;
[RelayCommand(CanExecute = nameof(Foo))]
private void GreetUser(string name)
{
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0011");
}
[TestMethod]
public void InvalidCanExecuteMember_MethodWithIncompatibleInputType_TooManyInputs()
{
string source = """
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel
{
private bool Foo(string name, int age) => true;
[RelayCommand(CanExecute = nameof(Foo))]
private void GreetUser(string name)
{
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0011");
}
[TestMethod]
public void InvalidRelayCommandAllowConcurrentExecutionsOption()
{
string source = """
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel
{
[RelayCommand(AllowConcurrentExecutions = false)]
private void GreetUser(User user)
{
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0012");
}
[TestMethod]
public void InvalidRelayCommandIncludeCancelCommandSettings_SynchronousMethod()
{
string source = """
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel
{
[RelayCommand(IncludeCancelCommand = true)]
private void GreetUser(User user)
{
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0013");
}
[TestMethod]
public void InvalidRelayCommandIncludeCancelCommandSettings_AsynchronousMethodWithNoCancellationToken()
{
string source = """
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel
{
[RelayCommand(IncludeCancelCommand = true)]
private async Task DoWorkAsync()
{
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0013");
}
[TestMethod]
public void InvalidRelayCommandIncludeCancelCommandSettings_AsynchronousMethodWithParameterAndNoCancellationToken()
{
string source = """
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel
{
[RelayCommand(IncludeCancelCommand = true)]
private async Task GreetUserAsync(User user)
{
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0013");
}
[TestMethod]
public async Task NameCollisionForGeneratedObservableProperty_PascalCaseField_Warns()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
private string {|MVVMTK0014:Name|};
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<PropertyNameCollisionObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public async Task NameCollisionForGeneratedObservableProperty_CamelCaseField_DoesNotWarn()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
private string name;
}
}
""";
// Using C# 9 here because the generated code will emit [MemberNotNull] on the property setter, which requires C# 9
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<PropertyNameCollisionObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp9);
}
[TestMethod]
public async Task NameCollisionForGeneratedObservableProperty_PascalCaseProperty_DoesNotWarn()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
private string Name { get; set; }
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<PropertyNameCollisionObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public void NotifyPropertyChangedForInvalidTargetError_Null()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
[NotifyPropertyChangedFor(null)]
private string name;
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0015");
}
[TestMethod]
public void NotifyPropertyChangedForInvalidTargetError_SamePropertyAsGeneratedOneFromSelf()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(Name))]
private string name;
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0015");
}
[TestMethod]
public void NotifyPropertyChangedForInvalidTargetError_Missing()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
[NotifyPropertyChangedFor("FooBar")]
private string name;
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0015");
}
[TestMethod]
public void NotifyPropertyChangedForInvalidTargetError_InvalidType()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(Foo))]
private string name;
public void Foo()
{
}
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0015");
}
[TestMethod]
public void NotifyCanExecuteChangedForInvalidTargetError_Null()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
[NotifyCanExecuteChangedFor(null)]
private string name;
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0016");
}
[TestMethod]
public void NotifyCanExecuteChangedForInvalidTargetError_Missing()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
[NotifyCanExecuteChangedFor("FooBar")]
private string name;
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0016");
}
[TestMethod]
public void NotifyCanExecuteChangedForInvalidTargetError_InvalidMemberType()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(Foo))]
private string name;
public void Foo()
{
}
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0016");
}
[TestMethod]
public void NotifyCanExecuteChangedForInvalidTargetError_InvalidPropertyType()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(Foo))]
private string name;
public string Foo { get; }
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0016");
}
[TestMethod]
public void NotifyCanExecuteChangedForInvalidTargetError_InvalidCommandType()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(FooCommand))]
private string name;
public ICommand FooCommand { get; }
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0016");
}
[TestMethod]
public void InvalidAttributeCombinationForINotifyPropertyChangedAttributeError_InheritingINotifyPropertyChangedAttribute()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[INotifyPropertyChanged]
public partial class A
{
}
[INotifyPropertyChanged]
public partial class B : A
{
}
}
""";
VerifyGeneratedDiagnostics<INotifyPropertyChangedGenerator>(source, "MVVMTK0017");
}
[TestMethod]
public void InvalidAttributeCombinationForINotifyPropertyChangedAttributeError_InheritingObservableObjectAttribute()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[ObservableObject]
public partial class A
{
}
[INotifyPropertyChanged]
public partial class B : A
{
}
}
""";
VerifyGeneratedDiagnostics<INotifyPropertyChangedGenerator>(source, "MVVMTK0017");
}
[TestMethod]
public void InvalidAttributeCombinationForINotifyPropertyChangedAttributeError_WithAlsoObservableObjectAttribute()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[INotifyPropertyChanged]
[ObservableObject]
public partial class A
{
}
}
""";
VerifyGeneratedDiagnostics<INotifyPropertyChangedGenerator>(source, "MVVMTK0017");
}
[TestMethod]
public void InvalidAttributeCombinationForObservableObjectAttributeError_InheritingINotifyPropertyChangedAttribute()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[INotifyPropertyChanged]
public partial class A
{
}
[ObservableObject]
public partial class B : A
{
}
}
""";
VerifyGeneratedDiagnostics<ObservableObjectGenerator>(source, "MVVMTK0018");
}
[TestMethod]
public void InvalidAttributeCombinationForObservableObjectAttributeError_InheritingObservableObjectAttribute()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[ObservableObject]
public partial class A
{
}
[ObservableObject]
public partial class B : A
{
}
}
""";
VerifyGeneratedDiagnostics<ObservableObjectGenerator>(source, "MVVMTK0018");
}
[TestMethod]
public void InvalidAttributeCombinationForObservableObjectAttributeError_WithAlsoINotifyPropertyChangedAttribute()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[INotifyPropertyChanged]
[ObservableObject]
public partial class A
{
}
}
""";
VerifyGeneratedDiagnostics<ObservableObjectGenerator>(source, "MVVMTK0018");
}
[TestMethod]
public async Task InvalidContainingTypeForObservableProperty_OnField_Warns()
{
string source = """
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class MyViewModel : INotifyPropertyChanged
{
[ObservableProperty]
public int {|MVVMTK0019:number|};
public event PropertyChangedEventHandler PropertyChanged;
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<InvalidTargetObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public async Task InvalidContainingTypeForObservableProperty_OnField_InValidType_DoesNotWarn()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class MyViewModel : ObservableObject
{
[ObservableProperty]
public int number;
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<InvalidTargetObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public async Task InvalidContainingTypeForObservableProperty_OnPartialProperty_Warns()
{
string source = """
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class MyViewModel : INotifyPropertyChanged
{
[ObservableProperty]
public int {|MVVMTK0019:Number|} { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<InvalidTargetObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public async Task InvalidContainingTypeForObservableProperty_OnPartialProperty_InValidType_DoesNotWarn()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class MyViewModel : ObservableObject
{
[ObservableProperty]
public int Number { get; set; }
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<InvalidTargetObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public async Task FieldWithOrphanedDependentObservablePropertyAttributesError_NotifyPropertyChangedFor()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class MyViewModel
{
[NotifyPropertyChangedFor("")]
public int {|MVVMTK0020:number|};
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<FieldWithOrphanedDependentObservablePropertyAttributesAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public async Task FieldWithOrphanedDependentObservablePropertyAttributesError_NotifyCanExecuteChangedFor()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class MyViewModel
{
[NotifyCanExecuteChangedFor("")]
public int {|MVVMTK0020:number|};
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<FieldWithOrphanedDependentObservablePropertyAttributesAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public async Task FieldWithOrphanedDependentObservablePropertyAttributesError_NotifyPropertyChangedRecipients()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class MyViewModel
{
[NotifyPropertyChangedRecipients]
public int {|MVVMTK0020:number|};
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<FieldWithOrphanedDependentObservablePropertyAttributesAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public async Task FieldWithOrphanedDependentObservablePropertyAttributesError_MultipleUsesStillGenerateOnlyASingleDiagnostic()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class MyViewModel
{
[NotifyPropertyChangedFor("")]
[NotifyPropertyChangedFor("")]
[NotifyPropertyChangedFor("")]
[NotifyCanExecuteChangedFor("")]
[NotifyCanExecuteChangedFor("")]
[NotifyPropertyChangedRecipients]
public int {|MVVMTK0020:number|};
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<FieldWithOrphanedDependentObservablePropertyAttributesAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public void InvalidAttributeCombinationForObservableRecipientAttributeError()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[ObservableRecipient]
public partial class A : ObservableObject
{
}
[ObservableRecipient]
public partial class B : A
{
}
}
""";
VerifyGeneratedDiagnostics<ObservableRecipientGenerator>(source, "MVVMTK0021");
}
[TestMethod]
public void InvalidContainingTypeForNotifyPropertyChangedRecipientsFieldError_ObservableObject()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class MyViewModel : ObservableObject
{
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public int number;
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0022");
}
[TestMethod]
public void MultipleRelayCommandMethodOverloads_WithOverloads()
{
string source = """
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel
{
[RelayCommand]
private void GreetUser()
{
}
[RelayCommand]
private void GreetUser(object value)
{
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0023");
}
[TestMethod]
public void MultipleRelayCommandMethodOverloads_WithOverloadInBaseType()
{
string source = """
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class BaseViewModel
{
[RelayCommand]
private void GreetUser()
{
}
}
public partial class SampleViewModel : BaseViewModel
{
[RelayCommand]
private void GreetUser(object value)
{
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0023");
}
[TestMethod]
public async Task InvalidObservablePropertyError_Object()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class MyViewModel : ObservableObject
{
[ObservableProperty]
public object {|MVVMTK0024:property|};
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<InvalidGeneratedPropertyObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp9);
}
[TestMethod]
public async Task InvalidObservablePropertyError_Object_WithProperty()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class MyViewModel : ObservableObject
{
[ObservableProperty]
public object {|MVVMTK0024:Property|} { get; set; }
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<InvalidGeneratedPropertyObservablePropertyAttributeAnalyzer>(source, LanguageVersion.Preview);
}
[TestMethod]
public async Task InvalidObservablePropertyError_PropertyChangingEventArgs()
{
string source = """
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class MyViewModel : ObservableObject
{
[ObservableProperty]
public PropertyChangingEventArgs {|MVVMTK0024:property|};
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<InvalidGeneratedPropertyObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp9);
}
[TestMethod]
public async Task InvalidObservablePropertyError_PropertyChangedEventArgs()
{
string source = """
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class MyViewModel : ObservableObject
{
[ObservableProperty]
public PropertyChangedEventArgs {|MVVMTK0024:property|};
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<InvalidGeneratedPropertyObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp9);
}
[TestMethod]
public async Task InvalidObservablePropertyError_CustomTypeDerivedFromPropertyChangedEventArgs()
{
string source = """
using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public class MyPropertyChangedEventArgs : PropertyChangedEventArgs
{
public MyPropertyChangedEventArgs(string propertyName)
: base(propertyName)
{
}
}
public partial class MyViewModel : ObservableObject
{
[ObservableProperty]
public MyPropertyChangedEventArgs {|MVVMTK0024:property|};
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<InvalidGeneratedPropertyObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp9);
}
[TestMethod]
public void MissingObservableValidatorInheritanceForNotifyDataErrorInfoError()
{
string source = """
using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[INotifyPropertyChanged]
public partial class SampleViewModel
{
[ObservableProperty]
[Required]
[NotifyDataErrorInfo]
private string name;
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0006", "MVVMTK0025");
}
[TestMethod]
public void MissingValidationAttributesForNotifyDataErrorInfoError()
{
string source = """
using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class SampleViewModel : ObservableValidator
{
[ObservableProperty]
[NotifyDataErrorInfo]
private string name;
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0026");
}
[TestMethod]
public async Task InvalidTypeForNotifyPropertyChangedRecipientsError()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[NotifyPropertyChangedRecipients]
public partial class {|MVVMTK0027:MyViewModel|} : ObservableObject
{
[ObservableProperty]
public int number;
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<InvalidClassLevelNotifyPropertyChangedRecipientsAttributeAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public async Task InvalidTypeForNotifyDataErrorInfoError()
{
string source = """
using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[NotifyDataErrorInfo]
public partial class {|MVVMTK0028:SampleViewModel|} : ObservableObject
{
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<InvalidClassLevelNotifyDataErrorInfoAttributeAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public void UnnecessaryNotifyPropertyChangedRecipientsWarning_SameType()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[NotifyPropertyChangedRecipients]
public partial class MyViewModel : ObservableRecipient
{
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public int number;
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0029");
}
[TestMethod]
public void UnnecessaryNotifyPropertyChangedRecipientsWarning_BaseType()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[NotifyPropertyChangedRecipients]
public class MyBaseViewModel : ObservableRecipient
{
}
public partial class MyViewModel : MyBaseViewModel
{
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public int number;
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0029");
}
[TestMethod]
public void UnnecessaryNotifyDataErrorInfoWarning_SameType()
{
string source = """
using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[NotifyDataErrorInfo]
public partial class MyViewModel : ObservableValidator
{
[ObservableProperty]
[Required]
[NotifyDataErrorInfo]
public int number;
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0030");
}
[TestMethod]
public void UnnecessaryNotifyDataErrorInfoWarning_BaseType()
{
string source = """
using System.ComponentModel.DataAnnotations;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[NotifyDataErrorInfo]
public class MyBaseViewModel : ObservableValidator
{
}
public partial class MyViewModel : MyBaseViewModel
{
[ObservableProperty]
[Required]
[NotifyDataErrorInfo]
public int number;
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0030");
}
[TestMethod]
public void InvalidRelayCommandFlowExceptionsToTaskSchedulerOption()
{
string source = """
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel
{
[RelayCommand(FlowExceptionsToTaskScheduler = false)]
private void GreetUser(User user)
{
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0031");
}
[TestMethod]
public async Task UsingINotifyPropertyChangedAttributeOnClassWithNoBaseType()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[INotifyPropertyChanged]
public partial class {|MVVMTK0032:SampleViewModel|}
{
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<ClassUsingAttributeInsteadOfInheritanceAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public async Task UsingObservableObjectAttributeOnClassWithNoBaseType()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
[ObservableObject]
public partial class {|MVVMTK0033:SampleViewModel|}
{
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<ClassUsingAttributeInsteadOfInheritanceAnalyzer>(source, LanguageVersion.CSharp8);
}
// See https://github.com/CommunityToolkit/dotnet/issues/518
[TestMethod]
public async Task FieldReferenceToFieldWithObservablePropertyAttribute_Assignment()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
partial class MyViewModel : ObservableObject
{
[ObservableProperty]
int number;
void M1()
{
{|MVVMTK0034:number|} = 1;
}
void M2()
{
{|MVVMTK0034:this.number|} = 1;
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<FieldReferenceForObservablePropertyFieldAnalyzer>(source, LanguageVersion.CSharp8);
}
// See https://github.com/CommunityToolkit/dotnet/issues/518
[TestMethod]
public async Task FieldReferenceToFieldWithObservablePropertyAttribute_Read()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
partial class MyViewModel : ObservableObject
{
[ObservableProperty]
int number;
void M()
{
var temp = {|MVVMTK0034:number|};
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<FieldReferenceForObservablePropertyFieldAnalyzer>(source, LanguageVersion.CSharp8);
}
// See https://github.com/CommunityToolkit/dotnet/issues/518
[TestMethod]
public async Task FieldReferenceToFieldWithObservablePropertyAttribute_Ref()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
partial class MyViewModel : ObservableObject
{
[ObservableProperty]
int number;
void M()
{
ref var x = ref {|MVVMTK0034:number|};
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<FieldReferenceForObservablePropertyFieldAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public async Task FieldReferenceToFieldWithObservablePropertyAttribute_DoesNotEmitWarningFromWithinConstructor()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
partial class MyViewModel : ObservableObject
{
[ObservableProperty]
int number;
public MyViewModel()
{
number = 42;
var temp = number;
ref var x = ref number;
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<FieldReferenceForObservablePropertyFieldAnalyzer>(source, LanguageVersion.CSharp8);
}
// See https://github.com/CommunityToolkit/dotnet/issues/563
[TestMethod]
public void InvalidPropertyTargetedAttributeOnObservablePropertyField_MissingUsingDirective()
{
string source = """
using System;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class MyViewModel : ObservableObject
{
[ObservableProperty]
[property: MyTest]
public int number;
}
}
namespace MyAttributes
{
[AttributeUsage(AttributeTargets.Property)]
public class MyTestAttribute : Attribute
{
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0035");
}
[TestMethod]
public void InvalidPropertyTargetedAttributeOnObservablePropertyField_TypoInAttributeName()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class MyViewModel : ObservableObject
{
[ObservableProperty]
[property: Fbuifbweif]
public int number;
}
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0035");
}
// See https://github.com/CommunityToolkit/dotnet/issues/683
[TestMethod]
public void InvalidPropertyTargetedAttributeOnObservablePropertyField_InvalidExpression()
{
string source = """
using System;
using CommunityToolkit.Mvvm.ComponentModel;
public partial class MyViewModel : ObservableObject
{
[ObservableProperty]
[property: Test(TestAttribute.M)]
private int number;
}
public class TestAttribute : Attribute
{
public static string M => "";
}
""";
VerifyGeneratedDiagnostics<ObservablePropertyGenerator>(source, "MVVMTK0037");
}
[TestMethod]
public void InvalidPropertyTargetedAttributeOnRelayCommandMethod_MissingUsingDirective()
{
string source = """
using System;
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class MyViewModel
{
[RelayCommand]
[property: MyTest]
private void Test()
{
}
}
}
namespace MyAttributes
{
[AttributeUsage(AttributeTargets.Property)]
public class MyTestAttribute : Attribute
{
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0036");
}
[TestMethod]
public void InvalidPropertyTargetedAttributeOnRelayCommandMethod_TypoInAttributeName()
{
string source = """
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class MyViewModel
{
[RelayCommand]
[property: Fbuifbweif]
private void Test()
{
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0036");
}
// See https://github.com/CommunityToolkit/dotnet/issues/683
[TestMethod]
public void InvalidPropertyTargetedAttributeOnRelayCommandMethod_InvalidExpressionOnFieldAttribute()
{
string source = """
using System;
using CommunityToolkit.Mvvm.Input;
public partial class MyViewModel
{
[RelayCommand]
[field: Test(TestAttribute.M)]
private void Test()
{
}
}
public class TestAttribute : Attribute
{
public static string M => "";
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0038");
}
// See https://github.com/CommunityToolkit/dotnet/issues/683
[TestMethod]
public void InvalidPropertyTargetedAttributeOnRelayCommandMethod_InvalidExpressionOnPropertyAttribute()
{
string source = """
using System;
using CommunityToolkit.Mvvm.Input;
public partial class MyViewModel
{
[RelayCommand]
[property: Test(TestAttribute.M)]
private void Test()
{
}
}
public class TestAttribute : Attribute
{
public static string M => "";
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0038");
}
[TestMethod]
public async Task AsyncVoidReturningRelayCommandMethodAnalyzer()
{
string source = """
using System;
using CommunityToolkit.Mvvm.Input;
public partial class MyViewModel
{
[RelayCommand]
private async void {|MVVMTK0039:TestAsync|}()
{
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<AsyncVoidReturningRelayCommandMethodAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public async Task FieldTargetedObservablePropertyAttribute_InstanceAutoProperty()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[field: {|MVVMTK0040:ObservableProperty|}]
public string Name { get; set; }
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<AutoPropertyWithFieldTargetedObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public async Task FieldTargetedObservablePropertyAttribute_StaticAutoProperty()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[field: {|MVVMTK0040:ObservableProperty|}]
public static string Name { get; set; }
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<AutoPropertyWithFieldTargetedObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp8);
}
[TestMethod]
public async Task FieldTargetedObservablePropertyAttribute_RecordPrimaryConstructorParameter()
{
string source = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp
{
public partial record SampleViewModel([field: {|MVVMTK0040:ObservableProperty|}] string Name);
}
namespace System.Runtime.CompilerServices
{
internal static class IsExternalInit
{
}
}
""";
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<AutoPropertyWithFieldTargetedObservablePropertyAttributeAnalyzer>(source, LanguageVersion.CSharp9);
}
[TestMethod]
public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_NotTargetingWindows_DoesNotWarn()
{
const string source = """
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[RelayCommand]
private void DoStuff()
{
}
}
}
""";
await CSharpAnalyzerWithLanguageVersionTest<WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer>.VerifyAnalyzerAsync(
source,
LanguageVersion.CSharp10,
editorconfig: []);
}
[TestMethod]
public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_TargetingWindows_DoesNotWarn()
{
const string source = """
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class SampleViewModel : ObservableObject
{
[RelayCommand]
private void DoStuff()
{
}
}
}
""";
await CSharpAnalyzerWithLanguageVersionTest<WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer>.VerifyAnalyzerAsync(
source,
LanguageVersion.CSharp10,
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
}
[TestMethod]
public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_TargetingWindows_NotIncluded1_DoesNotWarn()
{
const string source = """
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using WinRT;
namespace MyApp
{
[GeneratedBindableCustomProperty([], [])]
public partial class SampleViewModel : ObservableObject
{
[RelayCommand]
private void DoStuff()
{
}
}
}
namespace WinRT
{
public class GeneratedBindableCustomPropertyAttribute(string[] a, string[] b) : Attribute
{
}
}
""";
await CSharpAnalyzerWithLanguageVersionTest<WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer>.VerifyAnalyzerAsync(
source,
LanguageVersion.CSharp12,
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
}
[TestMethod]
public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_TargetingWindows_NotIncluded2_DoesNotWarn()
{
const string source = """
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using WinRT;
namespace MyApp
{
[GeneratedBindableCustomProperty(["OtherCommand"], [])]
public partial class SampleViewModel : ObservableObject
{
[RelayCommand]
private void DoStuff()
{
}
}
}
namespace WinRT
{
public class GeneratedBindableCustomPropertyAttribute(string[] a, string[] b) : Attribute
{
}
}
""";
await CSharpAnalyzerWithLanguageVersionTest<WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer>.VerifyAnalyzerAsync(
source,
LanguageVersion.CSharp12,
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
}
[TestMethod]
public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_TargetingWindows_NotIncluded3_DoesNotWarn()
{
const string source = """
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using WinRT;
namespace MyApp
{
[GeneratedBindableCustomProperty(["OtherCommand"], ["DoStuffCommand"])]
public partial class SampleViewModel : ObservableObject
{
[RelayCommand]
private void DoStuff()
{
}
}
}
namespace WinRT
{
public class GeneratedBindableCustomPropertyAttribute(string[] a, string[] b) : Attribute
{
}
}
""";
await CSharpAnalyzerWithLanguageVersionTest<WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer>.VerifyAnalyzerAsync(
source,
LanguageVersion.CSharp12,
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
}
[TestMethod]
public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_TargetingWindows_Bindable_Warns()
{
const string source = """
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using WinRT;
namespace MyApp
{
[GeneratedBindableCustomProperty]
public partial class SampleViewModel : ObservableObject
{
[RelayCommand]
private void {|MVVMTK0046:DoStuff|}()
{
}
}
}
namespace WinRT
{
public class GeneratedBindableCustomPropertyAttribute : Attribute
{
}
}
""";
await CSharpAnalyzerWithLanguageVersionTest<WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer>.VerifyAnalyzerAsync(
source,
LanguageVersion.CSharp10,
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
}
[TestMethod]
public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_TargetingWindows_Bindable_Included1_Warns()
{
const string source = """
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using WinRT;
namespace MyApp
{
[GeneratedBindableCustomProperty(["DoStuffCommand"], [])]
public partial class SampleViewModel : ObservableObject
{
[RelayCommand]
private void {|MVVMTK0046:DoStuff|}()
{
}
}
}
namespace WinRT
{
public class GeneratedBindableCustomPropertyAttribute(string[] a, string[] b) : Attribute
{
}
}
""";
await CSharpAnalyzerWithLanguageVersionTest<WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer>.VerifyAnalyzerAsync(
source,
LanguageVersion.CSharp12,
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
}
[TestMethod]
public async Task WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer_TargetingWindows_Bindable_Included2_Warns()
{
const string source = """
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using WinRT;
namespace MyApp
{
[GeneratedBindableCustomProperty(["Blah", "", "DoStuffCommand"], [])]
public partial class SampleViewModel : ObservableObject
{
[RelayCommand]
private void {|MVVMTK0046:DoStuff|}()
{
}
}
}
namespace WinRT
{
public class GeneratedBindableCustomPropertyAttribute(string[] a, string[] b) : Attribute
{
}
}
""";
await CSharpAnalyzerWithLanguageVersionTest<WinRTRelayCommandIsNotGeneratedBindableCustomPropertyCompatibleAnalyzer>.VerifyAnalyzerAsync(
source,
LanguageVersion.CSharp12,
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
}
[TestMethod]
public void RelayCommandWithOverriddenCanExecute_DoesNotWarn()
{
const string source = """
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class BaseViewModel : ObservableObject
{
protected virtual bool CanDoStuff()
{
return false;
}
}
public partial class SampleViewModel : BaseViewModel
{
[RelayCommand(CanExecute = nameof(CanDoStuff))]
private void DoStuff()
{
}
protected override bool CanDoStuff()
{
return true;
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, LanguageVersion.CSharp12);
}
[TestMethod]
public void RelayCommandWithOverriddenCanExecute_WithOneMethodNotInTheSameHierarchy_Warns()
{
const string source = """
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace MyApp
{
public partial class BaseViewModel : ObservableObject
{
protected virtual bool CanDoStuff()
{
return false;
}
protected bool CanDoStuff(string x)
{
}
}
public partial class SampleViewModel : BaseViewModel
{
[RelayCommand(CanExecute = nameof(CanDoStuff)]
private void DoStuff()
{
}
private override bool CanDoStuff()
{
return true;
}
}
}
""";
VerifyGeneratedDiagnostics<RelayCommandGenerator>(source, "MVVMTK0010");
}
[TestMethod]
public async Task WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer_NotTargetingWindows_DoesNotWarn()
{
const string source = """
using System;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp;
[ObservableObject]
public partial class SampleViewModel : BaseType
{
}
public class BaseType
{
}
""";
await CSharpAnalyzerWithLanguageVersionTest<WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer>.VerifyAnalyzerAsync(
source,
LanguageVersion.CSharp10,
editorconfig: []);
}
[TestMethod]
public async Task WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer_TargetingWindows_NoCsWinRTAotOptimizer_DoesNotWarn()
{
const string source = """
using System;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp;
[ObservableObject]
public partial class SampleViewModel : BaseType
{
}
public class BaseType
{
}
""";
await CSharpAnalyzerWithLanguageVersionTest<WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer>.VerifyAnalyzerAsync(
source,
LanguageVersion.CSharp10,
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true)]);
}
[TestMethod]
public async Task WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer_TargetingWindows_NoBaseType_ObservableObject_DoesNotWarn()
{
const string source = """
using System;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp;
[ObservableObject]
public partial class SampleViewModel
{
}
""";
await CSharpAnalyzerWithLanguageVersionTest<WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer>.VerifyAnalyzerAsync(
source,
LanguageVersion.CSharp10,
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "auto")]);
}
[TestMethod]
public async Task WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer_TargetingWindows_NoBaseType_INotifyPropertyChanged_DoesNotWarn()
{
const string source = """
using System;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp;
[INotifyPropertyChanged]
public partial class SampleViewModel
{
}
""";
await CSharpAnalyzerWithLanguageVersionTest<WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer>.VerifyAnalyzerAsync(
source,
LanguageVersion.CSharp10,
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "auto")]);
}
[TestMethod]
public async Task WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer_TargetingWindows_BaseType_ObservableObject_Warns()
{
const string source = """
using System;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp;
[ObservableObject]
public partial class {|MVVMTK0050:SampleViewModel|} : BaseType
{
}
public class BaseType
{
}
""";
await CSharpAnalyzerWithLanguageVersionTest<WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer>.VerifyAnalyzerAsync(
source,
LanguageVersion.CSharp10,
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "auto")]);
}
[TestMethod]
public async Task WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer_TargetingWindows_BaseType_INotifyPropertyChanged_Warns()
{
const string source = """
using System;
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp;
[INotifyPropertyChanged]
public partial class {|MVVMTK0049:SampleViewModel|} : BaseType
{
}
public class BaseType
{
}
""";
await CSharpAnalyzerWithLanguageVersionTest<WinRTClassUsingNotifyPropertyChangedAttributesAnalyzer>.VerifyAnalyzerAsync(
source,
LanguageVersion.CSharp10,
editorconfig: [("_MvvmToolkitIsUsingWindowsRuntimePack", true), ("CsWinRTAotOptimizerEnabled", "auto")]);
}
/// <summary>
/// Verifies the diagnostic errors for a given analyzer, and that all available source generators can run successfully with the input source (including subsequent compilation).
/// </summary>
/// <typeparam name="TAnalyzer">The type of the analyzer to test.</typeparam>
/// <param name="markdownSource">The input source to process with diagnostic annotations.</param>
/// <param name="languageVersion">The language version to use to parse code and run tests.</param>
internal static async Task VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<TAnalyzer>(string markdownSource, LanguageVersion languageVersion)
where TAnalyzer : DiagnosticAnalyzer, new()
{
await VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<TAnalyzer>(markdownSource, languageVersion, [], []);
}
/// <summary>
/// Verifies the diagnostic errors for a given analyzer, and that all available source generators can run successfully with the input source (including subsequent compilation).
/// </summary>
/// <typeparam name="TAnalyzer">The type of the analyzer to test.</typeparam>
/// <param name="markdownSource">The input source to process with diagnostic annotations.</param>
/// <param name="languageVersion">The language version to use to parse code and run tests.</param>
/// <param name="generatorDiagnosticsIds">The diagnostic ids to expect for the input source code.</param>
/// <param name="ignoredDiagnosticIds">The list of diagnostic ids to ignore in the final compilation.</param>
internal static async Task VerifyAnalyzerDiagnosticsAndSuccessfulGeneration<TAnalyzer>(string markdownSource, LanguageVersion languageVersion, string[] generatorDiagnosticsIds, string[] ignoredDiagnosticIds)
where TAnalyzer : DiagnosticAnalyzer, new()
{
await CSharpAnalyzerWithLanguageVersionTest<TAnalyzer>.VerifyAnalyzerAsync(markdownSource, languageVersion);
IIncrementalGenerator[] generators =
{
new IMessengerRegisterAllGenerator(),
new ObservableObjectGenerator(),
new INotifyPropertyChangedGenerator(),
new ObservablePropertyGenerator(),
new ObservableRecipientGenerator(),
new ObservableValidatorValidateAllPropertiesGenerator(),
new RelayCommandGenerator()
};
// Transform diagnostic annotations back to normal C# (eg. "{|MVVMTK0008:Foo()|}" ---> "Foo()")
string source = Regex.Replace(markdownSource, @"{\|((?:,?\w+)+):(.+?)\|}", m => m.Groups[2].Value);
VerifyGeneratedDiagnostics(CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(languageVersion)), generators, generatorDiagnosticsIds, ignoredDiagnosticIds);
}
/// <summary>
/// Verifies the output of a source generator.
/// </summary>
/// <typeparam name="TGenerator">The generator type to use.</typeparam>
/// <param name="source">The input source to process.</param>
/// <param name="diagnosticsIds">The diagnostic ids to expect for the input source code.</param>
internal static void VerifyGeneratedDiagnostics<TGenerator>(string source, params string[] diagnosticsIds)
where TGenerator : class, IIncrementalGenerator, new()
{
VerifyGeneratedDiagnostics<TGenerator>(source, LanguageVersion.CSharp8, diagnosticsIds);
}
/// <summary>
/// Verifies the output of a source generator.
/// </summary>
/// <typeparam name="TGenerator">The generator type to use.</typeparam>
/// <param name="source">The input source to process.</param>
/// <param name="languageVersion">The language version to use to parse code and run tests.</param>
/// <param name="diagnosticsIds">The diagnostic ids to expect for the input source code.</param>
internal static void VerifyGeneratedDiagnostics<TGenerator>(string source, LanguageVersion languageVersion, params string[] diagnosticsIds)
where TGenerator : class, IIncrementalGenerator, new()
{
IIncrementalGenerator generator = new TGenerator();
VerifyGeneratedDiagnostics(CSharpSyntaxTree.ParseText(source, CSharpParseOptions.Default.WithLanguageVersion(languageVersion)), new[] { generator }, diagnosticsIds, []);
}
/// <summary>
/// Verifies the output of one or more source generators.
/// </summary>
/// <param name="syntaxTree">The input source tree to process.</param>
/// <param name="generators">The generators to apply to the input syntax tree.</param>
/// <param name="generatorDiagnosticsIds">The diagnostic ids to expect for the input source code.</param>
/// <param name="ignoredDiagnosticIds">The list of diagnostic ids to ignore in the final compilation.</param>
internal static void VerifyGeneratedDiagnostics(SyntaxTree syntaxTree, IIncrementalGenerator[] generators, string[] generatorDiagnosticsIds, string[] ignoredDiagnosticIds)
{
// Ensure CommunityToolkit.Mvvm and System.ComponentModel.DataAnnotations are loaded
Type observableObjectType = typeof(ObservableObject);
Type validationAttributeType = typeof(ValidationAttribute);
// Get all assembly references for the loaded assemblies (easy way to pull in all necessary dependencies)
IEnumerable<MetadataReference> references =
from assembly in AppDomain.CurrentDomain.GetAssemblies()
where !assembly.IsDynamic
let reference = MetadataReference.CreateFromFile(assembly.Location)
select reference;
// Create a syntax tree with the input source
CSharpCompilation compilation = CSharpCompilation.Create(
"original",
[syntaxTree],
references,
new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
GeneratorDriver driver = CSharpGeneratorDriver.Create(generators).WithUpdatedParseOptions((CSharpParseOptions)syntaxTree.Options);
// Run all source generators on the input source code
_ = driver.RunGeneratorsAndUpdateCompilation(compilation, out Compilation outputCompilation, out ImmutableArray<Diagnostic> diagnostics);
string[] resultingIds = diagnostics.Select(diagnostic => diagnostic.Id).ToArray();
CollectionAssert.AreEquivalent(generatorDiagnosticsIds, resultingIds, $"resultingIds: {string.Join(", ", resultingIds)}");
// If the compilation was supposed to succeed, ensure that no further errors were generated
if (resultingIds.Length == 0)
{
// Compute diagnostics for the final compiled output (just include errors)
List<Diagnostic> outputCompilationDiagnostics = outputCompilation.GetDiagnostics().Where(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error).ToList();
// Filtered diagnostics
List<Diagnostic> filteredDiagnostics = outputCompilationDiagnostics.Where(diagnostic => !ignoredDiagnosticIds.Contains(diagnostic.Id)).ToList();
Assert.IsEmpty(filteredDiagnostics, $"resultingIds: {string.Join(", ", filteredDiagnostics)}");
}
GC.KeepAlive(observableObjectType);
GC.KeepAlive(validationAttributeType);
}
}