2 changed files with 420 additions and 131 deletions
@ -1,72 +1,289 @@ |
|||||
using System; |
using System; |
||||
using System.Reactive.Linq; |
using System.Collections; |
||||
|
using System.ComponentModel; |
||||
using Avalonia.Controls; |
using Avalonia.Controls; |
||||
using Avalonia.Data; |
using Avalonia.Data; |
||||
using Avalonia.Data.Core; |
using Avalonia.Styling; |
||||
using Avalonia.Markup.Data; |
using Avalonia.UnitTests; |
||||
using Xunit; |
using Xunit; |
||||
|
|
||||
|
#nullable enable |
||||
|
|
||||
namespace Avalonia.Markup.UnitTests.Data |
namespace Avalonia.Markup.UnitTests.Data |
||||
{ |
{ |
||||
public class BindingTests_DataValidation |
public class BindingTests_DataValidation |
||||
{ |
{ |
||||
[Fact] |
public abstract class TestBase<T> |
||||
public void Initiate_Should_Not_Enable_Data_Validation_With_BindingPriority_LocalValue() |
where T : AvaloniaProperty<int> |
||||
{ |
{ |
||||
var textBlock = new TextBlock |
[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() |
||||
{ |
{ |
||||
DataContext = new Class1(), |
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.", target.DataValidationError?.Message); |
||||
|
|
||||
|
target.SetValue(property, 10); |
||||
|
|
||||
var target = new Binding(nameof(Class1.Foo)); |
Assert.Equal(10, target.GetValue(property)); |
||||
var instanced = target.Initiate(textBlock, TextBlock.TextProperty, enableDataValidation: false); |
Assert.Null(target.DataValidationError); |
||||
var subject = (BindingExpression)instanced.Source; |
} |
||||
object result = null; |
|
||||
|
|
||||
subject.Subscribe(x => result = x); |
private protected abstract (DataValidationTestControl, T) CreateTarget(); |
||||
|
} |
||||
|
|
||||
Assert.IsType<string>(result); |
public class DirectPropertyTests : TestBase<DirectPropertyBase<int>> |
||||
|
{ |
||||
|
private protected override (DataValidationTestControl, DirectPropertyBase<int>) CreateTarget() |
||||
|
{ |
||||
|
return (new ValidatedDirectPropertyClass(), ValidatedDirectPropertyClass.ValueProperty); |
||||
|
} |
||||
} |
} |
||||
|
|
||||
[Fact] |
public class StyledPropertyTests : TestBase<StyledProperty<int>> |
||||
public void Initiate_Should_Enable_Data_Validation_With_BindingPriority_LocalValue() |
|
||||
{ |
{ |
||||
var textBlock = new TextBlock |
[Fact] |
||||
|
public void Style_Binding_Supports_Indei_Data_Validation() |
||||
|
{ |
||||
|
var (target, property) = CreateTarget(); |
||||
|
var binding = new Binding(nameof(IndeiValidatingModel.Value)) |
||||
|
{ |
||||
|
Mode = BindingMode.TwoWay |
||||
|
}; |
||||
|
|
||||
|
var root = new TestRoot |
||||
|
{ |
||||
|
DataContext = new IndeiValidatingModel(), |
||||
|
Styles = |
||||
|
{ |
||||
|
new Style(x => x.Is<DataValidationTestControl>()) |
||||
|
{ |
||||
|
Setters = |
||||
|
{ |
||||
|
new Setter(property, binding) |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
Child = target, |
||||
|
}; |
||||
|
|
||||
|
root.LayoutManager.ExecuteInitialLayoutPass(); |
||||
|
|
||||
|
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.", target.DataValidationError?.Message); |
||||
|
|
||||
|
target.SetValue(property, 10); |
||||
|
|
||||
|
Assert.Equal(10, target.GetValue(property)); |
||||
|
Assert.Null(target.DataValidationError); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Style_With_Activator_Binding_Supports_Indei_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.", 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.", target.DataValidationError?.Message); |
||||
|
|
||||
|
target.SetValue(property, 10); |
||||
|
|
||||
|
Assert.Equal(10, target.GetValue(property)); |
||||
|
Assert.Null(target.DataValidationError); |
||||
|
} |
||||
|
|
||||
|
private protected override (DataValidationTestControl, StyledProperty<int>) CreateTarget() |
||||
{ |
{ |
||||
DataContext = new Class1(), |
return (new ValidatedStyledPropertyClass(), ValidatedStyledPropertyClass.ValueProperty); |
||||
}; |
} |
||||
|
} |
||||
|
|
||||
var target = new Binding(nameof(Class1.Foo)); |
internal class DataValidationTestControl : Control |
||||
var instanced = target.Initiate(textBlock, TextBlock.TextProperty, enableDataValidation: true); |
{ |
||||
var subject = (BindingExpression)instanced.Source; |
public Exception? DataValidationError { get; protected set; } |
||||
object result = null; |
} |
||||
|
|
||||
subject.Subscribe(x => result = x); |
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); |
||||
|
} |
||||
|
|
||||
Assert.Equal(new BindingNotification("foo"), result); |
protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error) |
||||
|
{ |
||||
|
if (property == ValueProperty) |
||||
|
{ |
||||
|
DataValidationError = state.HasAnyFlag(BindingValueType.DataValidationError) ? error : null; |
||||
|
} |
||||
|
} |
||||
} |
} |
||||
|
|
||||
[Fact] |
private class ValidatedDirectPropertyClass : DataValidationTestControl |
||||
public void Initiate_Should_Not_Enable_Data_Validation_With_BindingPriority_TemplatedParent() |
|
||||
{ |
{ |
||||
var textBlock = new TextBlock |
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 |
||||
{ |
{ |
||||
DataContext = new Class1(), |
get => _value; |
||||
}; |
set => SetAndRaise(ValueProperty, ref _value, value); |
||||
|
} |
||||
|
|
||||
var target = new Binding(nameof(Class1.Foo)) { Priority = BindingPriority.Template }; |
protected override void UpdateDataValidation(AvaloniaProperty property, BindingValueType state, Exception? error) |
||||
var instanced = target.Initiate(textBlock, TextBlock.TextProperty, enableDataValidation: true); |
{ |
||||
var subject = (BindingExpression)instanced.Source; |
if (property == ValueProperty) |
||||
object result = null; |
{ |
||||
|
DataValidationError = state.HasAnyFlag(BindingValueType.DataValidationError) ? error : null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
subject.Subscribe(x => result = x); |
private class ExceptionValidatingModel |
||||
|
{ |
||||
|
public const int MaxValue = 100; |
||||
|
private int _value = 20; |
||||
|
|
||||
Assert.IsType<string>(result); |
public int Value |
||||
|
{ |
||||
|
get => _value; |
||||
|
set |
||||
|
{ |
||||
|
if (value > MaxValue) |
||||
|
throw new ArgumentOutOfRangeException(nameof(value)); |
||||
|
_value = value; |
||||
|
} |
||||
|
} |
||||
} |
} |
||||
|
|
||||
private class Class1 |
private class IndeiValidatingModel : INotifyDataErrorInfo |
||||
{ |
{ |
||||
public string Foo { get; set; } = "foo"; |
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."; |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
Loading…
Reference in new issue