|
|
@@ -1,115 +1,212 @@
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
-using System.Linq;
|
|
|
using System.Reactive.Subjects;
|
|
|
using Avalonia.Data;
|
|
|
using Avalonia.UnitTests;
|
|
|
using Xunit;
|
|
|
|
|
|
+#nullable enable
|
|
|
+
|
|
|
namespace Avalonia.Base.UnitTests
|
|
|
{
|
|
|
public class AvaloniaObjectTests_DataValidation
|
|
|
{
|
|
|
- [Fact]
|
|
|
- public void Binding_Non_Validated_Styled_Property_Does_Not_Call_UpdateDataValidation()
|
|
|
+ public abstract class TestBase<T>
|
|
|
+ where T : AvaloniaProperty<int>
|
|
|
{
|
|
|
- var target = new Class1();
|
|
|
- var source = new Subject<BindingValue<int>>();
|
|
|
+ [Fact]
|
|
|
+ public void Binding_Non_Validated_Property_Does_Not_Call_UpdateDataValidation()
|
|
|
+ {
|
|
|
+ var target = new Class1();
|
|
|
+ var source = new Subject<BindingValue<int>>();
|
|
|
+ var property = GetNonValidatedProperty();
|
|
|
|
|
|
- target.Bind(Class1.NonValidatedProperty, source);
|
|
|
- source.OnNext(6);
|
|
|
- source.OnNext(BindingValue<int>.BindingError(new Exception()));
|
|
|
- source.OnNext(BindingValue<int>.DataValidationError(new Exception()));
|
|
|
- source.OnNext(6);
|
|
|
+ target.Bind(property, source);
|
|
|
+ source.OnNext(6);
|
|
|
+ source.OnNext(BindingValue<int>.BindingError(new Exception()));
|
|
|
+ source.OnNext(BindingValue<int>.DataValidationError(new Exception()));
|
|
|
+ source.OnNext(6);
|
|
|
|
|
|
- Assert.Empty(target.Notifications);
|
|
|
- }
|
|
|
+ Assert.Empty(target.Notifications);
|
|
|
+ }
|
|
|
|
|
|
- [Fact]
|
|
|
- public void Binding_Non_Validated_Direct_Property_Does_Not_Call_UpdateDataValidation()
|
|
|
- {
|
|
|
- var target = new Class1();
|
|
|
- var source = new Subject<BindingValue<int>>();
|
|
|
+ [Fact]
|
|
|
+ public void Binding_Validated_Property_Calls_UpdateDataValidation()
|
|
|
+ {
|
|
|
+ var target = new Class1();
|
|
|
+ var source = new Subject<BindingValue<int>>();
|
|
|
+ var property = GetProperty();
|
|
|
+ var error1 = new Exception();
|
|
|
+ var error2 = new Exception();
|
|
|
|
|
|
- target.Bind(Class1.NonValidatedDirectProperty, source);
|
|
|
- source.OnNext(6);
|
|
|
- source.OnNext(BindingValue<int>.BindingError(new Exception()));
|
|
|
- source.OnNext(BindingValue<int>.DataValidationError(new Exception()));
|
|
|
- source.OnNext(6);
|
|
|
+ target.Bind(property, source);
|
|
|
+ source.OnNext(6);
|
|
|
+ source.OnNext(BindingValue<int>.DataValidationError(error1));
|
|
|
+ source.OnNext(BindingValue<int>.BindingError(error2));
|
|
|
+ source.OnNext(7);
|
|
|
|
|
|
- Assert.Empty(target.Notifications);
|
|
|
- }
|
|
|
+ Assert.Equal(new Notification[]
|
|
|
+ {
|
|
|
+ new(BindingValueType.Value, 6, null),
|
|
|
+ new(BindingValueType.DataValidationError, 6, error1),
|
|
|
+ new(BindingValueType.BindingError, 0, error2),
|
|
|
+ new(BindingValueType.Value, 7, null),
|
|
|
+ }, target.Notifications);
|
|
|
+ }
|
|
|
|
|
|
- [Fact]
|
|
|
- public void Binding_Validated_Direct_Property_Calls_UpdateDataValidation()
|
|
|
- {
|
|
|
- var target = new Class1();
|
|
|
- var source = new Subject<BindingValue<int>>();
|
|
|
-
|
|
|
- target.Bind(Class1.ValidatedDirectIntProperty, source);
|
|
|
- source.OnNext(6);
|
|
|
- source.OnNext(BindingValue<int>.BindingError(new Exception()));
|
|
|
- source.OnNext(BindingValue<int>.DataValidationError(new Exception()));
|
|
|
- source.OnNext(7);
|
|
|
-
|
|
|
- var result = target.Notifications;
|
|
|
- Assert.Equal(4, result.Count);
|
|
|
- Assert.Equal(BindingValueType.Value, result[0].type);
|
|
|
- Assert.Equal(6, result[0].value);
|
|
|
- Assert.Equal(BindingValueType.BindingError, result[1].type);
|
|
|
- Assert.Equal(BindingValueType.DataValidationError, result[2].type);
|
|
|
- Assert.Equal(BindingValueType.Value, result[3].type);
|
|
|
- Assert.Equal(7, result[3].value);
|
|
|
+ [Fact]
|
|
|
+ public void Binding_Validated_Property_Calls_UpdateDataValidation_Untyped()
|
|
|
+ {
|
|
|
+ var target = new Class1();
|
|
|
+ var source = new Subject<object>();
|
|
|
+ var property = GetProperty();
|
|
|
+ var error1 = new Exception();
|
|
|
+ var error2 = new Exception();
|
|
|
+
|
|
|
+ target.Bind(property, source);
|
|
|
+ source.OnNext(6);
|
|
|
+ source.OnNext(new BindingNotification(error1, BindingErrorType.DataValidationError));
|
|
|
+ source.OnNext(new BindingNotification(error2, BindingErrorType.Error));
|
|
|
+ source.OnNext(7);
|
|
|
+
|
|
|
+ Assert.Equal(new Notification[]
|
|
|
+ {
|
|
|
+ new(BindingValueType.Value, 6, null),
|
|
|
+ new(BindingValueType.DataValidationError, 6, error1),
|
|
|
+ new(BindingValueType.BindingError, 0, error2),
|
|
|
+ new(BindingValueType.Value, 7, null),
|
|
|
+ }, target.Notifications);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void Binding_Overridden_Validated_Property_Calls_UpdateDataValidation()
|
|
|
+ {
|
|
|
+ var target = new Class2();
|
|
|
+ var source = new Subject<BindingValue<int>>();
|
|
|
+ var property = GetNonValidatedProperty();
|
|
|
+
|
|
|
+ // Class2 overrides the non-validated property metadata to enable data validation.
|
|
|
+ target.Bind(property, source);
|
|
|
+ source.OnNext(1);
|
|
|
+
|
|
|
+ Assert.Equal(1, target.Notifications.Count);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void Disposing_Binding_Subscription_Clears_DataValidation()
|
|
|
+ {
|
|
|
+ var target = new Class1();
|
|
|
+ var source = new Subject<BindingValue<int>>();
|
|
|
+ var property = GetProperty();
|
|
|
+ var error = new Exception();
|
|
|
+ var sub = target.Bind(property, source);
|
|
|
+
|
|
|
+ source.OnNext(6);
|
|
|
+ source.OnNext(BindingValue<int>.DataValidationError(error));
|
|
|
+ sub.Dispose();
|
|
|
+
|
|
|
+ Assert.Equal(new Notification[]
|
|
|
+ {
|
|
|
+ new(BindingValueType.Value, 6, null),
|
|
|
+ new(BindingValueType.DataValidationError, 6, error),
|
|
|
+ new(BindingValueType.UnsetValue, 6, null),
|
|
|
+ }, target.Notifications);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void Completing_Binding_Clears_DataValidation()
|
|
|
+ {
|
|
|
+ var target = new Class1();
|
|
|
+ var source = new Subject<BindingValue<int>>();
|
|
|
+ var property = GetProperty();
|
|
|
+ var error = new Exception();
|
|
|
+
|
|
|
+ target.Bind(property, source);
|
|
|
+ source.OnNext(6);
|
|
|
+ source.OnNext(BindingValue<int>.DataValidationError(error));
|
|
|
+ source.OnCompleted();
|
|
|
+
|
|
|
+ Assert.Equal(new Notification[]
|
|
|
+ {
|
|
|
+ new(BindingValueType.Value, 6, null),
|
|
|
+ new(BindingValueType.DataValidationError, 6, error),
|
|
|
+ new(BindingValueType.UnsetValue, 6, null),
|
|
|
+ }, target.Notifications);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected abstract T GetProperty();
|
|
|
+ protected abstract T GetNonValidatedProperty();
|
|
|
}
|
|
|
|
|
|
- [Fact]
|
|
|
- public void Binding_Overridden_Validated_Direct_Property_Calls_UpdateDataValidation()
|
|
|
+ public class DirectPropertyTests : TestBase<DirectPropertyBase<int>>
|
|
|
{
|
|
|
- var target = new Class2();
|
|
|
- var source = new Subject<BindingValue<int>>();
|
|
|
+ [Fact]
|
|
|
+ public void Bound_Validated_String_Property_Can_Be_Set_To_Null()
|
|
|
+ {
|
|
|
+ var source = new ViewModel
|
|
|
+ {
|
|
|
+ StringValue = "foo",
|
|
|
+ };
|
|
|
|
|
|
- // Class2 overrides `NonValidatedDirectProperty`'s metadata to enable data validation.
|
|
|
- target.Bind(Class1.NonValidatedDirectProperty, source);
|
|
|
- source.OnNext(1);
|
|
|
+ var target = new Class1
|
|
|
+ {
|
|
|
+ [!Class1.ValidatedDirectStringProperty] = new Binding
|
|
|
+ {
|
|
|
+ Path = nameof(ViewModel.StringValue),
|
|
|
+ Source = source,
|
|
|
+ },
|
|
|
+ };
|
|
|
+
|
|
|
+ Assert.Equal("foo", target.ValidatedDirectString);
|
|
|
|
|
|
- Assert.Equal(1, target.Notifications.Count);
|
|
|
+ source.StringValue = null;
|
|
|
+
|
|
|
+ Assert.Null(target.ValidatedDirectString);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override DirectPropertyBase<int> GetProperty() => Class1.ValidatedDirectIntProperty;
|
|
|
+ protected override DirectPropertyBase<int> GetNonValidatedProperty() => Class1.NonValidatedDirectIntProperty;
|
|
|
}
|
|
|
|
|
|
- [Fact]
|
|
|
- public void Bound_Validated_Direct_String_Property_Can_Be_Set_To_Null()
|
|
|
+ public class StyledPropertyTests : TestBase<StyledProperty<int>>
|
|
|
{
|
|
|
- var source = new ViewModel
|
|
|
+ [Fact]
|
|
|
+ public void Bound_Validated_String_Property_Can_Be_Set_To_Null()
|
|
|
{
|
|
|
- StringValue = "foo",
|
|
|
- };
|
|
|
+ var source = new ViewModel
|
|
|
+ {
|
|
|
+ StringValue = "foo",
|
|
|
+ };
|
|
|
|
|
|
- var target = new Class1
|
|
|
- {
|
|
|
- [!Class1.ValidatedDirectStringProperty] = new Binding
|
|
|
+ var target = new Class1
|
|
|
{
|
|
|
- Path = nameof(ViewModel.StringValue),
|
|
|
- Source = source,
|
|
|
- },
|
|
|
- };
|
|
|
+ [!Class1.ValidatedDirectStringProperty] = new Binding
|
|
|
+ {
|
|
|
+ Path = nameof(ViewModel.StringValue),
|
|
|
+ Source = source,
|
|
|
+ },
|
|
|
+ };
|
|
|
|
|
|
- Assert.Equal("foo", target.ValidatedDirectString);
|
|
|
+ Assert.Equal("foo", target.ValidatedDirectString);
|
|
|
|
|
|
- source.StringValue = null;
|
|
|
+ source.StringValue = null;
|
|
|
|
|
|
- Assert.Null(target.ValidatedDirectString);
|
|
|
+ Assert.Null(target.ValidatedDirectString);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override StyledProperty<int> GetProperty() => Class1.ValidatedStyledIntProperty;
|
|
|
+ protected override StyledProperty<int> GetNonValidatedProperty() => Class1.NonValidatedStyledIntProperty;
|
|
|
}
|
|
|
|
|
|
+ private record class Notification(BindingValueType type, object? value, Exception? error);
|
|
|
+
|
|
|
private class Class1 : AvaloniaObject
|
|
|
{
|
|
|
- public static readonly StyledProperty<int> NonValidatedProperty =
|
|
|
- AvaloniaProperty.Register<Class1, int>(
|
|
|
- nameof(NonValidated));
|
|
|
-
|
|
|
- public static readonly DirectProperty<Class1, int> NonValidatedDirectProperty =
|
|
|
+ public static readonly DirectProperty<Class1, int> NonValidatedDirectIntProperty =
|
|
|
AvaloniaProperty.RegisterDirect<Class1, int>(
|
|
|
- nameof(NonValidatedDirect),
|
|
|
- o => o.NonValidatedDirect,
|
|
|
- (o, v) => o.NonValidatedDirect = v);
|
|
|
+ nameof(NonValidatedDirectInt),
|
|
|
+ o => o.NonValidatedDirectInt,
|
|
|
+ (o, v) => o.NonValidatedDirectInt = v);
|
|
|
|
|
|
public static readonly DirectProperty<Class1, int> ValidatedDirectIntProperty =
|
|
|
AvaloniaProperty.RegisterDirect<Class1, int>(
|
|
|
@@ -118,27 +215,30 @@ namespace Avalonia.Base.UnitTests
|
|
|
(o, v) => o.ValidatedDirectInt = v,
|
|
|
enableDataValidation: true);
|
|
|
|
|
|
- public static readonly DirectProperty<Class1, string> ValidatedDirectStringProperty =
|
|
|
- AvaloniaProperty.RegisterDirect<Class1, string>(
|
|
|
+ public static readonly DirectProperty<Class1, string?> ValidatedDirectStringProperty =
|
|
|
+ AvaloniaProperty.RegisterDirect<Class1, string?>(
|
|
|
nameof(ValidatedDirectString),
|
|
|
o => o.ValidatedDirectString,
|
|
|
(o, v) => o.ValidatedDirectString = v,
|
|
|
enableDataValidation: true);
|
|
|
|
|
|
+ public static readonly StyledProperty<int> NonValidatedStyledIntProperty =
|
|
|
+ AvaloniaProperty.Register<Class1, int>(
|
|
|
+ nameof(NonValidatedStyledInt));
|
|
|
+
|
|
|
+ public static readonly StyledProperty<int> ValidatedStyledIntProperty =
|
|
|
+ AvaloniaProperty.Register<Class1, int>(
|
|
|
+ nameof(ValidatedStyledInt),
|
|
|
+ enableDataValidation: true);
|
|
|
+
|
|
|
private int _nonValidatedDirect;
|
|
|
private int _directInt;
|
|
|
- private string _directString;
|
|
|
+ private string? _directString;
|
|
|
|
|
|
- public int NonValidated
|
|
|
- {
|
|
|
- get { return GetValue(NonValidatedProperty); }
|
|
|
- set { SetValue(NonValidatedProperty, value); }
|
|
|
- }
|
|
|
-
|
|
|
- public int NonValidatedDirect
|
|
|
+ public int NonValidatedDirectInt
|
|
|
{
|
|
|
get { return _directInt; }
|
|
|
- set { SetAndRaise(NonValidatedDirectProperty, ref _nonValidatedDirect, value); }
|
|
|
+ set { SetAndRaise(NonValidatedDirectIntProperty, ref _nonValidatedDirect, value); }
|
|
|
}
|
|
|
|
|
|
public int ValidatedDirectInt
|
|
|
@@ -147,20 +247,32 @@ namespace Avalonia.Base.UnitTests
|
|
|
set { SetAndRaise(ValidatedDirectIntProperty, ref _directInt, value); }
|
|
|
}
|
|
|
|
|
|
- public string ValidatedDirectString
|
|
|
+ public string? ValidatedDirectString
|
|
|
{
|
|
|
get { return _directString; }
|
|
|
set { SetAndRaise(ValidatedDirectStringProperty, ref _directString, value); }
|
|
|
}
|
|
|
|
|
|
- public List<(BindingValueType type, object value)> Notifications { get; } = new();
|
|
|
+ public int NonValidatedStyledInt
|
|
|
+ {
|
|
|
+ get { return GetValue(NonValidatedStyledIntProperty); }
|
|
|
+ set { SetValue(NonValidatedStyledIntProperty, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ public int ValidatedStyledInt
|
|
|
+ {
|
|
|
+ get => GetValue(ValidatedStyledIntProperty);
|
|
|
+ set => SetValue(ValidatedStyledIntProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public List<Notification> Notifications { get; } = new();
|
|
|
|
|
|
protected override void UpdateDataValidation(
|
|
|
AvaloniaProperty property,
|
|
|
BindingValueType state,
|
|
|
- Exception error)
|
|
|
+ Exception? error)
|
|
|
{
|
|
|
- Notifications.Add((state, GetValue(property)));
|
|
|
+ Notifications.Add(new(state, GetValue(property), error));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -168,16 +280,18 @@ namespace Avalonia.Base.UnitTests
|
|
|
{
|
|
|
static Class2()
|
|
|
{
|
|
|
- NonValidatedDirectProperty.OverrideMetadata<Class2>(
|
|
|
+ NonValidatedDirectIntProperty.OverrideMetadata<Class2>(
|
|
|
new DirectPropertyMetadata<int>(enableDataValidation: true));
|
|
|
+ NonValidatedStyledIntProperty.OverrideMetadata<Class2>(
|
|
|
+ new StyledPropertyMetadata<int>(enableDataValidation: true));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public class ViewModel : NotifyingBase
|
|
|
{
|
|
|
- private string _stringValue;
|
|
|
+ private string? _stringValue;
|
|
|
|
|
|
- public string StringValue
|
|
|
+ public string? StringValue
|
|
|
{
|
|
|
get { return _stringValue; }
|
|
|
set { _stringValue = value; RaisePropertyChanged(); }
|