Skip to content

Commit d2fe8a0

Browse files
committed
Add unit tests for [MemberNotNull] generation
1 parent c11bb0e commit d2fe8a0

File tree

2 files changed

+238
-1
lines changed

2 files changed

+238
-1
lines changed

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

Lines changed: 209 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ public class Test_SourceGeneratorsCodegen
2222
public void ObservablePropertyWithPartialMethodWithPreviousValuesNotUsed_DoesNotGenerateFieldReadAndMarksOldValueAsNullable()
2323
{
2424
string source = """
25-
using System.ComponentModel;
2625
using CommunityToolkit.Mvvm.ComponentModel;
2726
2827
#nullable enable
@@ -36,6 +35,7 @@ partial class MyViewModel : ObservableObject
3635
}
3736
""";
3837

38+
#if NET6_0_OR_GREATER
3939
string result = """
4040
// <auto-generated/>
4141
#pragma warning disable
@@ -50,6 +50,7 @@ partial class MyViewModel
5050
public string Name
5151
{
5252
get => name;
53+
[global::System.Diagnostics.CodeAnalysis.MemberNotNull("name")]
5354
set
5455
{
5556
if (!global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(name, value))
@@ -90,6 +91,213 @@ public string Name
9091
}
9192
}
9293
""";
94+
#else
95+
string result = """
96+
// <auto-generated/>
97+
#pragma warning disable
98+
#nullable enable
99+
namespace MyApp
100+
{
101+
partial class MyViewModel
102+
{
103+
/// <inheritdoc cref="name"/>
104+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
105+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
106+
public string Name
107+
{
108+
get => name;
109+
set
110+
{
111+
if (!global::System.Collections.Generic.EqualityComparer<string>.Default.Equals(name, value))
112+
{
113+
OnNameChanging(value);
114+
OnNameChanging(default, value);
115+
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
116+
name = value;
117+
OnNameChanged(value);
118+
OnNameChanged(default, value);
119+
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
120+
}
121+
}
122+
}
123+
124+
/// <summary>Executes the logic for when <see cref="Name"/> is changing.</summary>
125+
/// <param name="value">The new property value being set.</param>
126+
/// <remarks>This method is invoked right before the value of <see cref="Name"/> is changed.</remarks>
127+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
128+
partial void OnNameChanging(string value);
129+
/// <summary>Executes the logic for when <see cref="Name"/> is changing.</summary>
130+
/// <param name="oldValue">The previous property value that is being replaced.</param>
131+
/// <param name="newValue">The new property value being set.</param>
132+
/// <remarks>This method is invoked right before the value of <see cref="Name"/> is changed.</remarks>
133+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
134+
partial void OnNameChanging(string? oldValue, string newValue);
135+
/// <summary>Executes the logic for when <see cref="Name"/> just changed.</summary>
136+
/// <param name="value">The new property value that was set.</param>
137+
/// <remarks>This method is invoked right after the value of <see cref="Name"/> is changed.</remarks>
138+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
139+
partial void OnNameChanged(string value);
140+
/// <summary>Executes the logic for when <see cref="Name"/> just changed.</summary>
141+
/// <param name="oldValue">The previous property value that was replaced.</param>
142+
/// <param name="newValue">The new property value that was set.</param>
143+
/// <remarks>This method is invoked right after the value of <see cref="Name"/> is changed.</remarks>
144+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
145+
partial void OnNameChanged(string? oldValue, string newValue);
146+
}
147+
}
148+
""";
149+
#endif
150+
151+
VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
152+
}
153+
154+
[TestMethod]
155+
public void ObservablePropertyWithNullableReferenceType_DoesNotEmitMemberNotNullAttribute()
156+
{
157+
string source = """
158+
using CommunityToolkit.Mvvm.ComponentModel;
159+
160+
#nullable enable
161+
162+
namespace MyApp;
163+
164+
partial class MyViewModel : ObservableObject
165+
{
166+
[ObservableProperty]
167+
private string? name;
168+
}
169+
""";
170+
171+
string result = """
172+
// <auto-generated/>
173+
#pragma warning disable
174+
#nullable enable
175+
namespace MyApp
176+
{
177+
partial class MyViewModel
178+
{
179+
/// <inheritdoc cref="name"/>
180+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
181+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
182+
public string? Name
183+
{
184+
get => name;
185+
set
186+
{
187+
if (!global::System.Collections.Generic.EqualityComparer<string?>.Default.Equals(name, value))
188+
{
189+
OnNameChanging(value);
190+
OnNameChanging(default, value);
191+
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Name);
192+
name = value;
193+
OnNameChanged(value);
194+
OnNameChanged(default, value);
195+
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Name);
196+
}
197+
}
198+
}
199+
200+
/// <summary>Executes the logic for when <see cref="Name"/> is changing.</summary>
201+
/// <param name="value">The new property value being set.</param>
202+
/// <remarks>This method is invoked right before the value of <see cref="Name"/> is changed.</remarks>
203+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
204+
partial void OnNameChanging(string? value);
205+
/// <summary>Executes the logic for when <see cref="Name"/> is changing.</summary>
206+
/// <param name="oldValue">The previous property value that is being replaced.</param>
207+
/// <param name="newValue">The new property value being set.</param>
208+
/// <remarks>This method is invoked right before the value of <see cref="Name"/> is changed.</remarks>
209+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
210+
partial void OnNameChanging(string? oldValue, string? newValue);
211+
/// <summary>Executes the logic for when <see cref="Name"/> just changed.</summary>
212+
/// <param name="value">The new property value that was set.</param>
213+
/// <remarks>This method is invoked right after the value of <see cref="Name"/> is changed.</remarks>
214+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
215+
partial void OnNameChanged(string? value);
216+
/// <summary>Executes the logic for when <see cref="Name"/> just changed.</summary>
217+
/// <param name="oldValue">The previous property value that was replaced.</param>
218+
/// <param name="newValue">The new property value that was set.</param>
219+
/// <remarks>This method is invoked right after the value of <see cref="Name"/> is changed.</remarks>
220+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
221+
partial void OnNameChanged(string? oldValue, string? newValue);
222+
}
223+
}
224+
""";
225+
226+
VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
227+
}
228+
229+
[TestMethod]
230+
public void ObservablePropertyWithNonNullableValueType_DoesNotEmitMemberNotNullAttribute()
231+
{
232+
string source = """
233+
using System;
234+
using CommunityToolkit.Mvvm.ComponentModel;
235+
236+
#nullable enable
237+
238+
namespace MyApp;
239+
240+
partial class MyViewModel : ObservableObject
241+
{
242+
[ObservableProperty]
243+
private Guid id;
244+
}
245+
""";
246+
247+
string result = """
248+
// <auto-generated/>
249+
#pragma warning disable
250+
#nullable enable
251+
namespace MyApp
252+
{
253+
partial class MyViewModel
254+
{
255+
/// <inheritdoc cref="id"/>
256+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
257+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
258+
public global::System.Guid Id
259+
{
260+
get => id;
261+
set
262+
{
263+
if (!global::System.Collections.Generic.EqualityComparer<global::System.Guid>.Default.Equals(id, value))
264+
{
265+
OnIdChanging(value);
266+
OnIdChanging(default, value);
267+
OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangingArgs.Id);
268+
id = value;
269+
OnIdChanged(value);
270+
OnIdChanged(default, value);
271+
OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedArgs.Id);
272+
}
273+
}
274+
}
275+
276+
/// <summary>Executes the logic for when <see cref="Id"/> is changing.</summary>
277+
/// <param name="value">The new property value being set.</param>
278+
/// <remarks>This method is invoked right before the value of <see cref="Id"/> is changed.</remarks>
279+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
280+
partial void OnIdChanging(global::System.Guid value);
281+
/// <summary>Executes the logic for when <see cref="Id"/> is changing.</summary>
282+
/// <param name="oldValue">The previous property value that is being replaced.</param>
283+
/// <param name="newValue">The new property value being set.</param>
284+
/// <remarks>This method is invoked right before the value of <see cref="Id"/> is changed.</remarks>
285+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
286+
partial void OnIdChanging(global::System.Guid oldValue, global::System.Guid newValue);
287+
/// <summary>Executes the logic for when <see cref="Id"/> just changed.</summary>
288+
/// <param name="value">The new property value that was set.</param>
289+
/// <remarks>This method is invoked right after the value of <see cref="Id"/> is changed.</remarks>
290+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
291+
partial void OnIdChanged(global::System.Guid value);
292+
/// <summary>Executes the logic for when <see cref="Id"/> just changed.</summary>
293+
/// <param name="oldValue">The previous property value that was replaced.</param>
294+
/// <param name="newValue">The new property value that was set.</param>
295+
/// <remarks>This method is invoked right after the value of <see cref="Id"/> is changed.</remarks>
296+
[global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.2.0.0")]
297+
partial void OnIdChanged(global::System.Guid oldValue, global::System.Guid newValue);
298+
}
299+
}
300+
""";
93301

94302
VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
95303
}

tests/CommunityToolkit.Mvvm.UnitTests/Test_ObservablePropertyAttribute.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
using System.Collections.Generic;
77
using System.ComponentModel;
88
using System.ComponentModel.DataAnnotations;
9+
#if NET6_0_OR_GREATER
10+
using System.Diagnostics.CodeAnalysis;
11+
#endif
912
using System.Linq;
1013
using System.Reflection;
1114
#if NET6_0_OR_GREATER
@@ -1037,6 +1040,17 @@ public void Test_ObservableProperty_ModelWithObservablePropertyWithUnderscoreAnd
10371040
Assert.IsTrue(model.IsReadOnly);
10381041
}
10391042

1043+
#if NET6_0_OR_GREATER
1044+
[TestMethod]
1045+
public void Test_ObservableProperty_MemberNotNullAttributeIsPresent()
1046+
{
1047+
MemberNotNullAttribute? attribute = typeof(ModelWithNonNullableObservableProperty).GetProperty(nameof(ModelWithNonNullableObservableProperty.Name))!.SetMethod!.GetCustomAttribute<MemberNotNullAttribute>();
1048+
1049+
Assert.IsNotNull(attribute);
1050+
CollectionAssert.AreEqual(new[] { nameof(ModelWithNonNullableObservableProperty.name) }, attribute.Members);
1051+
}
1052+
#endif
1053+
10401054
public abstract partial class BaseViewModel : ObservableObject
10411055
{
10421056
public string? Content { get; set; }
@@ -1664,4 +1678,19 @@ private partial class ModelWithObservablePropertyWithUnderscoreAndUppercase : Ob
16641678
[ObservableProperty]
16651679
private bool _IsReadOnly;
16661680
}
1681+
1682+
#if NET6_0_OR_GREATER
1683+
// See https://github.com/CommunityToolkit/dotnet/issues/645
1684+
// This viewmodel is here only to double check no warnings are emitted when the attribute is present
1685+
public partial class ModelWithNonNullableObservableProperty : ObservableObject
1686+
{
1687+
public ModelWithNonNullableObservableProperty()
1688+
{
1689+
Name = "Bob";
1690+
}
1691+
1692+
[ObservableProperty]
1693+
internal string name;
1694+
}
1695+
#endif
16671696
}

0 commit comments

Comments
 (0)