123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- using System;
- using System.Collections;
- using System.ComponentModel;
- using Avalonia.Controls;
- using Avalonia.Data;
- using Avalonia.Styling;
- using Avalonia.UnitTests;
- using Xunit;
- #nullable enable
- namespace Avalonia.Markup.UnitTests.Data
- {
- public class BindingTests_DataValidation
- {
- public abstract class TestBase<T> : ScopedTestBase
- where T : AvaloniaProperty<int>
- {
- [Fact]
- public void Setter_Exception_Causes_DataValidation_Error()
- {
- var (target, property) = CreateTarget();
- var binding = new Binding(nameof(ExceptionValidatingModel.Value))
- {
- Mode = BindingMode.TwoWay
- };
- target.DataContext = new ExceptionValidatingModel();
- target.Bind(property, binding);
- Assert.Equal(20, target.GetValue(property));
- target.SetValue(property, 200);
- Assert.Equal(200, target.GetValue(property));
- Assert.IsType<ArgumentOutOfRangeException>(target.DataValidationError);
- target.SetValue(property, 10);
- Assert.Equal(10, target.GetValue(property));
- Assert.Null(target.DataValidationError);
- }
- [Fact]
- public void Indei_Error_Causes_DataValidation_Error()
- {
- var (target, property) = CreateTarget();
- var binding = new Binding(nameof(IndeiValidatingModel.Value))
- {
- Mode = BindingMode.TwoWay
- };
- target.DataContext = new IndeiValidatingModel();
- target.Bind(property, binding);
- Assert.Equal(20, target.GetValue(property));
- target.SetValue(property, 200);
- Assert.Equal(200, target.GetValue(property));
- Assert.IsType<DataValidationException>(target.DataValidationError);
- Assert.Equal("Invalid value: 200.", target.DataValidationError?.Message);
- target.SetValue(property, 10);
- Assert.Equal(10, target.GetValue(property));
- Assert.Null(target.DataValidationError);
- }
- [Fact]
- public void Disposing_Binding_Subscription_Clears_DataValidation()
- {
- var (target, property) = CreateTarget();
- var binding = new Binding(nameof(ExceptionValidatingModel.Value))
- {
- Mode = BindingMode.TwoWay
- };
- target.DataContext = new IndeiValidatingModel
- {
- Value = 200,
- };
-
- var sub = target.Bind(property, binding);
- Assert.Equal(200, target.GetValue(property));
- Assert.IsType<DataValidationException>(target.DataValidationError);
- sub.Dispose();
- Assert.Null(target.DataValidationError);
- }
- private protected abstract (DataValidationTestControl, T) CreateTarget();
- }
- public class DirectPropertyTests : TestBase<DirectPropertyBase<int>>
- {
- private protected override (DataValidationTestControl, DirectPropertyBase<int>) CreateTarget()
- {
- return (new ValidatedDirectPropertyClass(), ValidatedDirectPropertyClass.ValueProperty);
- }
- }
- public class StyledPropertyTests : TestBase<StyledProperty<int>>
- {
- [Fact]
- public void Style_Binding_Supports_Data_Validation()
- {
- var (target, property) = CreateTarget();
- var binding = new Binding(nameof(IndeiValidatingModel.Value))
- {
- Mode = BindingMode.TwoWay
- };
- var model = new IndeiValidatingModel();
- var root = new TestRoot
- {
- DataContext = model,
- Styles =
- {
- new Style(x => x.Is<DataValidationTestControl>())
- {
- Setters =
- {
- new Setter(property, binding)
- }
- }
- },
- Child = target,
- };
- root.LayoutManager.ExecuteInitialLayoutPass();
- Assert.Equal(20, target.GetValue(property));
- model.Value = 200;
- Assert.Equal(200, target.GetValue(property));
- Assert.IsType<DataValidationException>(target.DataValidationError);
- Assert.Equal("Invalid value: 200.", target.DataValidationError?.Message);
- model.Value = 10;
- Assert.Equal(10, target.GetValue(property));
- Assert.Null(target.DataValidationError);
- }
- [Fact]
- public void Style_With_Activator_Binding_Supports_Data_Validation()
- {
- var (target, property) = CreateTarget();
- var binding = new Binding(nameof(IndeiValidatingModel.Value))
- {
- Mode = BindingMode.TwoWay
- };
- var model = new IndeiValidatingModel
- {
- Value = 200,
- };
- var root = new TestRoot
- {
- DataContext = model,
- Styles =
- {
- new Style(x => x.Is<DataValidationTestControl>().Class("foo"))
- {
- Setters =
- {
- new Setter(property, binding)
- }
- }
- },
- Child = target,
- };
- root.LayoutManager.ExecuteInitialLayoutPass();
- target.Classes.Add("foo");
- Assert.Equal(200, target.GetValue(property));
- Assert.IsType<DataValidationException>(target.DataValidationError);
- Assert.Equal("Invalid value: 200.", target.DataValidationError?.Message);
- target.Classes.Remove("foo");
- Assert.Equal(0, target.GetValue(property));
- Assert.Null(target.DataValidationError);
- target.Classes.Add("foo");
- Assert.IsType<DataValidationException>(target.DataValidationError);
- Assert.Equal("Invalid value: 200.", target.DataValidationError?.Message);
- model.Value = 10;
- Assert.Equal(10, target.GetValue(property));
- Assert.Null(target.DataValidationError);
- }
- [Fact]
- public void Data_Validation_Can_Switch_Between_Style_And_LocalValue_Binding()
- {
- var (target, property) = CreateTarget();
- var model1 = new IndeiValidatingModel { Value = 200 };
- var model2 = new IndeiValidatingModel { Value = 300 };
- var binding1 = new Binding(nameof(IndeiValidatingModel.Value));
- var binding2 = new Binding(nameof(IndeiValidatingModel.Value)) { Source = model2 };
- var root = new TestRoot
- {
- DataContext = model1,
- Styles =
- {
- new Style(x => x.Is<DataValidationTestControl>())
- {
- Setters =
- {
- new Setter(property, binding1)
- }
- }
- },
- Child = target,
- };
- root.LayoutManager.ExecuteInitialLayoutPass();
- Assert.Equal(200, target.GetValue(property));
- Assert.IsType<DataValidationException>(target.DataValidationError);
- Assert.Equal("Invalid value: 200.", target.DataValidationError?.Message);
- var sub = target.Bind(property, binding2);
- Assert.Equal(300, target.GetValue(property));
- Assert.Equal("Invalid value: 300.", target.DataValidationError?.Message);
- sub.Dispose();
- Assert.Equal(200, target.GetValue(property));
- Assert.IsType<DataValidationException>(target.DataValidationError);
- Assert.Equal("Invalid value: 200.", target.DataValidationError?.Message);
- }
- [Fact]
- public void Data_Validation_Can_Switch_Between_Style_And_StyleTrigger_Binding()
- {
- var (target, property) = CreateTarget();
- var model1 = new IndeiValidatingModel { Value = 200 };
- var model2 = new IndeiValidatingModel { Value = 300 };
- var binding1 = new Binding(nameof(IndeiValidatingModel.Value));
- var binding2 = new Binding(nameof(IndeiValidatingModel.Value)) { Source = model2 };
- var root = new TestRoot
- {
- DataContext = model1,
- Styles =
- {
- new Style(x => x.Is<DataValidationTestControl>())
- {
- Setters =
- {
- new Setter(property, binding1)
- }
- },
- new Style(x => x.Is<DataValidationTestControl>().Class("foo"))
- {
- Setters =
- {
- new Setter(property, binding2)
- }
- },
- },
- Child = target,
- };
- root.LayoutManager.ExecuteInitialLayoutPass();
- Assert.Equal(200, target.GetValue(property));
- Assert.IsType<DataValidationException>(target.DataValidationError);
- Assert.Equal("Invalid value: 200.", target.DataValidationError?.Message);
- target.Classes.Add("foo");
- Assert.Equal(300, target.GetValue(property));
- Assert.Equal("Invalid value: 300.", target.DataValidationError?.Message);
- target.Classes.Remove("foo");
- Assert.Equal(200, target.GetValue(property));
- Assert.IsType<DataValidationException>(target.DataValidationError);
- Assert.Equal("Invalid value: 200.", target.DataValidationError?.Message);
- }
- private protected override (DataValidationTestControl, StyledProperty<int>) CreateTarget()
- {
- return (new ValidatedStyledPropertyClass(), ValidatedStyledPropertyClass.ValueProperty);
- }
- }
- internal class DataValidationTestControl : Control
- {
- public Exception? DataValidationError { get; protected set; }
- }
- private class ValidatedStyledPropertyClass : DataValidationTestControl
- {
- public static readonly StyledProperty<int> ValueProperty =
- AvaloniaProperty.Register<ValidatedStyledPropertyClass, int>(
- "Value",
- enableDataValidation: true);
- public int Value
- {
- get => GetValue(ValueProperty);
- set => SetValue(ValueProperty, value);
- }
- protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error)
- {
- if (property == ValueProperty)
- {
- DataValidationError = state.HasAnyFlag(BindingValueType.DataValidationError) ? error : null;
- }
- }
- }
- private class ValidatedDirectPropertyClass : DataValidationTestControl
- {
- public static readonly DirectProperty<ValidatedDirectPropertyClass, int> ValueProperty =
- AvaloniaProperty.RegisterDirect<ValidatedDirectPropertyClass, int>(
- "Value",
- o => o.Value,
- (o, v) => o.Value = v,
- enableDataValidation: true);
- private int _value;
- public int Value
- {
- get => _value;
- set => SetAndRaise(ValueProperty, ref _value, value);
- }
- protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error)
- {
- if (property == ValueProperty)
- {
- DataValidationError = state.HasAnyFlag(BindingValueType.DataValidationError) ? error : null;
- }
- }
- }
- private class ExceptionValidatingModel
- {
- public const int MaxValue = 100;
- private int _value = 20;
- public int Value
- {
- get => _value;
- set
- {
- if (value > MaxValue)
- throw new ArgumentOutOfRangeException(nameof(value));
- _value = value;
- }
- }
- }
- private class IndeiValidatingModel : INotifyDataErrorInfo
- {
- public const int MaxValue = 100;
- private bool _hasErrors;
- private int _value = 20;
- public int Value
- {
- get => _value;
- set
- {
- _value = value;
- HasErrors = value > MaxValue;
- }
- }
- public bool HasErrors
- {
- get => _hasErrors;
- private set
- {
- if (_hasErrors != value)
- {
- _hasErrors = value;
- ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(Value)));
- }
- }
- }
- public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;
- public IEnumerable GetErrors(string? propertyName)
- {
- if (propertyName == nameof(Value) && _value > MaxValue)
- yield return $"Invalid value: {_value}.";
- }
- }
- }
- }
|