Browse Source
As far as it was in `property-validation-grokys` branch. Things were messed up on master so had to merge this manually so may be problems.pull/537/head
41 changed files with 720 additions and 352 deletions
@ -0,0 +1,29 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using ReactiveUI; |
|||
using System; |
|||
|
|||
namespace BindingTest.ViewModels |
|||
{ |
|||
public class ExceptionPropertyErrorViewModel : ReactiveObject |
|||
{ |
|||
private int _lessThan10; |
|||
|
|||
public int LessThan10 |
|||
{ |
|||
get { return _lessThan10; } |
|||
set |
|||
{ |
|||
if (value < 10) |
|||
{ |
|||
this.RaiseAndSetIfChanged(ref _lessThan10, value); |
|||
} |
|||
else |
|||
{ |
|||
throw new InvalidOperationException("Value must be less than 10."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
namespace Avalonia.Data |
|||
{ |
|||
/// <summary>
|
|||
/// Contains information on if the current object passed validation.
|
|||
/// Subclasses of this class contain additional information depending on the method of validation checking.
|
|||
/// </summary>
|
|||
public interface IValidationStatus |
|||
{ |
|||
/// <summary>
|
|||
/// True when the data passes validation; otherwise, false.
|
|||
/// </summary>
|
|||
bool IsValid { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Avalonia.Data |
|||
{ |
|||
/// <summary>
|
|||
/// An immutable struct that contains validation information for a <see cref="AvaloniaObject"/> that validates a single property.
|
|||
/// </summary>
|
|||
public struct ObjectValidationStatus : IValidationStatus |
|||
{ |
|||
private Dictionary<Type, IValidationStatus> currentValidationStatus; |
|||
|
|||
public bool IsValid => currentValidationStatus?.Values.All(status => status.IsValid) ?? true; |
|||
|
|||
/// <summary>
|
|||
/// Constructs the structure with the given validation information.
|
|||
/// </summary>
|
|||
/// <param name="validations">The validation information</param>
|
|||
public ObjectValidationStatus(Dictionary<Type, IValidationStatus> validations) |
|||
:this() |
|||
{ |
|||
currentValidationStatus = validations; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates a new status with the updated information.
|
|||
/// </summary>
|
|||
/// <param name="status">The updated status information.</param>
|
|||
/// <returns>The new validation status.</returns>
|
|||
public ObjectValidationStatus UpdateValidationStatus(IValidationStatus status) |
|||
{ |
|||
var newStatus = new Dictionary<Type, IValidationStatus>(currentValidationStatus ?? |
|||
new Dictionary<Type, IValidationStatus>()); |
|||
newStatus[status.GetType()] = status; |
|||
return new ObjectValidationStatus(newStatus); |
|||
} |
|||
|
|||
public IEnumerable<IValidationStatus> StatusInformation => currentValidationStatus.Values; |
|||
} |
|||
} |
|||
@ -1,13 +0,0 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Data |
|||
{ |
|||
[Flags] |
|||
public enum ValidationMethods |
|||
{ |
|||
None = 0, |
|||
Exceptions = 1, |
|||
INotifyDataErrorInfo = 2, |
|||
All = -1 |
|||
} |
|||
} |
|||
@ -1,30 +0,0 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Avalonia.Data |
|||
{ |
|||
/// <summary>
|
|||
/// Contains information on if the current object passed validation.
|
|||
/// Subclasses of this class contain additional information depending on the method of validation checking.
|
|||
/// </summary>
|
|||
public abstract class ValidationStatus |
|||
{ |
|||
/// <summary>
|
|||
/// True when the data passes validation; otherwise, false.
|
|||
/// </summary>
|
|||
public abstract bool IsValid { get; } |
|||
|
|||
/// <summary>
|
|||
/// Checks if this validation status came from a currently enabled method of validation checking.
|
|||
/// </summary>
|
|||
/// <param name="enabledMethods">The enabled methods of validation checking.</param>
|
|||
/// <returns>True if enabled; otherwise, false.</returns>
|
|||
public abstract bool Match(ValidationMethods enabledMethods); |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
using Avalonia.Data; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class ControlValidationStatus : ValidationStatus, INotifyPropertyChanged |
|||
{ |
|||
private Dictionary<Type, ValidationStatus> propertyValidation = new Dictionary<Type, ValidationStatus>(); |
|||
|
|||
public override bool IsValid => propertyValidation.Values.All(status => status.IsValid); |
|||
|
|||
public event PropertyChangedEventHandler PropertyChanged; |
|||
|
|||
public override bool Match(ValidationMethods enabledMethods) => true; |
|||
|
|||
public void UpdateValidationStatus(ValidationStatus status) |
|||
{ |
|||
propertyValidation[status.GetType()] = status; |
|||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("")); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using System; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia.Markup.Data.Plugins |
|||
{ |
|||
|
|||
/// <summary>
|
|||
/// A base class for validating <see cref="IPropertyAccessor"/>s that wraps an <see cref="IPropertyAccessor"/> and forwards method calls to it.
|
|||
/// </summary>
|
|||
public abstract class ValidatingPropertyAccessorBase : IPropertyAccessor |
|||
{ |
|||
protected readonly WeakReference _reference; |
|||
protected readonly string _name; |
|||
private readonly IPropertyAccessor _accessor; |
|||
private readonly Action<IValidationStatus> _callback; |
|||
|
|||
protected ValidatingPropertyAccessorBase(WeakReference reference, string name, IPropertyAccessor accessor, Action<IValidationStatus> callback) |
|||
{ |
|||
_reference = reference; |
|||
_name = name; |
|||
_accessor = accessor; |
|||
_callback = callback; |
|||
} |
|||
|
|||
/// <inheritdoc/>
|
|||
public Type PropertyType => _accessor.PropertyType; |
|||
|
|||
/// <inheritdoc/>
|
|||
public object Value => _accessor.Value; |
|||
|
|||
/// <inheritdoc/>
|
|||
public virtual void Dispose() => _accessor.Dispose(); |
|||
|
|||
/// <inheritdoc/>
|
|||
public virtual bool SetValue(object value, BindingPriority priority) => _accessor.SetValue(value, priority); |
|||
|
|||
/// <summary>
|
|||
/// Sends the validation status to the callback specified in construction.
|
|||
/// </summary>
|
|||
/// <param name="status">The validation status.</param>
|
|||
protected void SendValidationCallback(IValidationStatus status) |
|||
{ |
|||
_callback?.Invoke(status); |
|||
} |
|||
} |
|||
} |
|||
@ -1,34 +0,0 @@ |
|||
using System; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia.Markup.Data.Plugins |
|||
{ |
|||
public abstract class ValidationCheckerBase : IPropertyAccessor |
|||
{ |
|||
protected readonly WeakReference _reference; |
|||
protected readonly string _name; |
|||
private readonly IPropertyAccessor _accessor; |
|||
private readonly Action<ValidationStatus> _callback; |
|||
|
|||
protected ValidationCheckerBase(WeakReference reference, string name, IPropertyAccessor accessor, Action<ValidationStatus> callback) |
|||
{ |
|||
_reference = reference; |
|||
_name = name; |
|||
_accessor = accessor; |
|||
_callback = callback; |
|||
} |
|||
|
|||
public Type PropertyType => _accessor.PropertyType; |
|||
|
|||
public object Value => _accessor.Value; |
|||
|
|||
public virtual void Dispose() => _accessor.Dispose(); |
|||
|
|||
public virtual bool SetValue(object value, BindingPriority priority) => _accessor.SetValue(value, priority); |
|||
|
|||
protected void SendValidationCallback(ValidationStatus status) |
|||
{ |
|||
_callback?.Invoke(status); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,130 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using Avalonia.Markup.Xaml.Data; |
|||
using Avalonia.UnitTests; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Controls.UnitTests |
|||
{ |
|||
public class TextBoxTests_ValidationState |
|||
{ |
|||
[Fact] |
|||
public void Setter_Exceptions_Should_Set_ValidationState() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) |
|||
{ |
|||
var target = new TextBox(); |
|||
var binding = new Binding(nameof(ExceptionTest.LessThan10)); |
|||
binding.Source = new ExceptionTest(); |
|||
binding.EnableValidation = true; |
|||
target.Bind(TextBox.TextProperty, binding); |
|||
|
|||
Assert.True(target.ValidationStatus.IsValid); |
|||
target.Text = "20"; |
|||
Assert.False(target.ValidationStatus.IsValid); |
|||
target.Text = "1"; |
|||
Assert.True(target.ValidationStatus.IsValid); |
|||
} |
|||
} |
|||
|
|||
[Fact(Skip = "TODO: Not yet passing")] |
|||
public void Unconvertable_Value_Should_Set_ValidationState() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) |
|||
{ |
|||
var target = new TextBox(); |
|||
var binding = new Binding(nameof(ExceptionTest.LessThan10)); |
|||
binding.Source = new ExceptionTest(); |
|||
binding.EnableValidation = true; |
|||
target.Bind(TextBox.TextProperty, binding); |
|||
|
|||
Assert.True(target.ValidationStatus.IsValid); |
|||
target.Text = "foo"; |
|||
Assert.False(target.ValidationStatus.IsValid); |
|||
target.Text = "1"; |
|||
Assert.True(target.ValidationStatus.IsValid); |
|||
} |
|||
} |
|||
|
|||
[Fact] |
|||
public void Indei_Should_Set_ValidationState() |
|||
{ |
|||
using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) |
|||
{ |
|||
var target = new TextBox(); |
|||
var binding = new Binding(nameof(ExceptionTest.LessThan10)); |
|||
binding.Source = new IndeiTest(); |
|||
binding.EnableValidation = true; |
|||
target.Bind(TextBox.TextProperty, binding); |
|||
|
|||
Assert.True(target.ValidationStatus.IsValid); |
|||
target.Text = "20"; |
|||
Assert.False(target.ValidationStatus.IsValid); |
|||
target.Text = "1"; |
|||
Assert.True(target.ValidationStatus.IsValid); |
|||
} |
|||
} |
|||
|
|||
private class ExceptionTest |
|||
{ |
|||
private int _lessThan10; |
|||
|
|||
public int LessThan10 |
|||
{ |
|||
get { return _lessThan10; } |
|||
set |
|||
{ |
|||
if (value < 10) |
|||
{ |
|||
_lessThan10 = value; |
|||
} |
|||
else |
|||
{ |
|||
throw new InvalidOperationException("More than 10."); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
private class IndeiTest : INotifyDataErrorInfo |
|||
{ |
|||
private int _lessThan10; |
|||
private Dictionary<string, IList<string>> _errors = new Dictionary<string, IList<string>>(); |
|||
|
|||
public int LessThan10 |
|||
{ |
|||
get { return _lessThan10; } |
|||
set |
|||
{ |
|||
if (value < 10) |
|||
{ |
|||
_lessThan10 = value; |
|||
_errors.Remove(nameof(LessThan10)); |
|||
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(LessThan10))); |
|||
} |
|||
else |
|||
{ |
|||
_errors[nameof(LessThan10)] = new[] { "More than 10" }; |
|||
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(LessThan10))); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public bool HasErrors => _lessThan10 >= 10; |
|||
|
|||
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; |
|||
|
|||
public IEnumerable GetErrors(string propertyName) |
|||
{ |
|||
IList<string> result; |
|||
_errors.TryGetValue(propertyName, out result); |
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,129 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
using System.Runtime.CompilerServices; |
|||
using Avalonia.Data; |
|||
using Avalonia.Markup.Data; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Markup.UnitTests.Data |
|||
{ |
|||
public class ExpressionObserverTests_Validation |
|||
{ |
|||
[Fact] |
|||
public void Exception_Validation_Sends_ValidationUpdate() |
|||
{ |
|||
var data = new ExceptionTest { MustBePositive = 5 }; |
|||
var observer = new ExpressionObserver(data, nameof(data.MustBePositive), false); |
|||
var validationMessageFound = false; |
|||
observer.Where(o => o is IValidationStatus).Subscribe(_ => validationMessageFound = true); |
|||
observer.SetValue(-5); |
|||
Assert.True(validationMessageFound); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Disabled_Indei_Validation_Does_Not_Subscribe() |
|||
{ |
|||
var data = new IndeiTest { MustBePositive = 5 }; |
|||
var observer = new ExpressionObserver(data, nameof(data.MustBePositive), false); |
|||
|
|||
observer.Subscribe(_ => { }); |
|||
|
|||
Assert.Equal(0, data.SubscriptionCount); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Enabled_Indei_Validation_Subscribes() |
|||
{ |
|||
var data = new IndeiTest { MustBePositive = 5 }; |
|||
var observer = new ExpressionObserver(data, nameof(data.MustBePositive), true); |
|||
var sub = observer.Subscribe(_ => { }); |
|||
|
|||
Assert.Equal(1, data.SubscriptionCount); |
|||
sub.Dispose(); |
|||
Assert.Equal(0, data.SubscriptionCount); |
|||
} |
|||
|
|||
public class ExceptionTest : INotifyPropertyChanged |
|||
{ |
|||
private int _mustBePositive; |
|||
|
|||
public int MustBePositive |
|||
{ |
|||
get { return _mustBePositive; } |
|||
set |
|||
{ |
|||
if (value <= 0) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(value)); |
|||
} |
|||
_mustBePositive = value; |
|||
} |
|||
} |
|||
|
|||
public event PropertyChangedEventHandler PropertyChanged; |
|||
|
|||
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") |
|||
{ |
|||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
|||
} |
|||
} |
|||
|
|||
private class IndeiTest : INotifyDataErrorInfo |
|||
{ |
|||
private int _mustBePositive; |
|||
private Dictionary<string, IList<string>> _errors = new Dictionary<string, IList<string>>(); |
|||
private EventHandler<DataErrorsChangedEventArgs> _errorsChanged; |
|||
|
|||
public int MustBePositive |
|||
{ |
|||
get { return _mustBePositive; } |
|||
set |
|||
{ |
|||
if (value >= 0) |
|||
{ |
|||
_mustBePositive = value; |
|||
_errors.Remove(nameof(MustBePositive)); |
|||
_errorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(MustBePositive))); |
|||
} |
|||
else |
|||
{ |
|||
_errors[nameof(MustBePositive)] = new[] { "Must be positive" }; |
|||
_errorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(nameof(MustBePositive))); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public bool HasErrors => _mustBePositive >= 0; |
|||
|
|||
public int SubscriptionCount { get; private set; } |
|||
|
|||
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged |
|||
{ |
|||
add |
|||
{ |
|||
_errorsChanged += value; |
|||
++SubscriptionCount; |
|||
} |
|||
remove |
|||
{ |
|||
_errorsChanged -= value; |
|||
--SubscriptionCount; |
|||
} |
|||
} |
|||
|
|||
public IEnumerable GetErrors(string propertyName) |
|||
{ |
|||
IList<string> result; |
|||
_errors.TryGetValue(propertyName, out result); |
|||
return result; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,111 +1,153 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Data; |
|||
using Avalonia.Markup.Xaml.Data; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel; |
|||
using System.Linq; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Markup.Xaml.UnitTests.Data |
|||
{ |
|||
public class BindingTests_Validation |
|||
{ |
|||
public class Data : INotifyPropertyChanged |
|||
[Fact] |
|||
public void Disabled_Validation_Should_Trigger_Validation_Change_On_Exception() |
|||
{ |
|||
private string mustbeNonEmpty; |
|||
|
|||
public string MustBeNonEmpty |
|||
var source = new ValidationTestModel { MustBePositive = 5 }; |
|||
var target = new TestControl { DataContext = source }; |
|||
var binding = new Binding |
|||
{ |
|||
get { return mustbeNonEmpty; } |
|||
set |
|||
{ |
|||
if (string.IsNullOrEmpty(value)) |
|||
{ |
|||
throw new ArgumentException(nameof(value)); |
|||
} |
|||
mustbeNonEmpty = value; |
|||
} |
|||
} |
|||
Path = nameof(source.MustBePositive), |
|||
Mode = BindingMode.TwoWay, |
|||
|
|||
public event PropertyChangedEventHandler PropertyChanged; |
|||
// Even though EnableValidation = false, exception validation is enabled.
|
|||
EnableValidation = false, |
|||
}; |
|||
|
|||
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") |
|||
{ |
|||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
|||
} |
|||
target.Bind(TestControl.ValidationTestProperty, binding); |
|||
|
|||
target.ValidationTest = -5; |
|||
|
|||
Assert.False(target.ValidationStatus.IsValid); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Disabled_Validation_Should_Not_Trigger_Validation_Change_Direct() |
|||
public void Enabled_Validation_Should_Trigger_Validation_Change_On_Exception() |
|||
{ |
|||
var source = new Data { MustBeNonEmpty = "Test" }; |
|||
var target = new TextBlock { DataContext = source }; |
|||
var source = new ValidationTestModel { MustBePositive = 5 }; |
|||
var target = new TestControl { DataContext = source }; |
|||
var binding = new Binding |
|||
{ |
|||
Path = nameof(source.MustBeNonEmpty), |
|||
Mode = Avalonia.Data.BindingMode.TwoWay, |
|||
ValidationMethods = Avalonia.Data.ValidationMethods.None |
|||
Path = nameof(source.MustBePositive), |
|||
Mode = BindingMode.TwoWay, |
|||
EnableValidation = true, |
|||
}; |
|||
target.Bind(TextBlock.TextProperty, binding); |
|||
|
|||
target.Text = ""; |
|||
|
|||
Assert.True(target.ValidationStatus.IsValid); |
|||
target.Bind(TestControl.ValidationTestProperty, binding); |
|||
|
|||
target.ValidationTest = -5; |
|||
Assert.False(target.ValidationStatus.IsValid); |
|||
} |
|||
|
|||
|
|||
[Fact] |
|||
public void Enabled_Validation_Should_Trigger_Validation_Change_Direct() |
|||
public void Passed_Validation_Should_Not_Add_Invalid_Pseudo_Class() |
|||
{ |
|||
var source = new Data { MustBeNonEmpty = "Test" }; |
|||
var target = new TextBlock { DataContext = source }; |
|||
var control = new TestControl(); |
|||
var model = new ValidationTestModel { MustBePositive = 1 }; |
|||
var binding = new Binding |
|||
{ |
|||
Path = nameof(source.MustBeNonEmpty), |
|||
Mode = Avalonia.Data.BindingMode.TwoWay, |
|||
ValidationMethods = Avalonia.Data.ValidationMethods.All |
|||
Path = nameof(model.MustBePositive), |
|||
Mode = BindingMode.TwoWay, |
|||
EnableValidation = true, |
|||
}; |
|||
target.Bind(TextBlock.TextProperty, binding); |
|||
|
|||
target.Text = ""; |
|||
Assert.False(target.ValidationStatus.IsValid); |
|||
|
|||
control.Bind(TestControl.ValidationTestProperty, binding); |
|||
control.DataContext = model; |
|||
Assert.DoesNotContain(control.Classes, x => x == ":invalid"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Disabled_Validation_Should_Not_Trigger_Validation_Change_Styled() |
|||
public void Failed_Validation_Should_Add_Invalid_Pseudo_Class() |
|||
{ |
|||
var source = new Data { MustBeNonEmpty = "Test" }; |
|||
var target = new TextBlock { DataContext = source }; |
|||
var control = new TestControl(); |
|||
var model = new ValidationTestModel { MustBePositive = 1 }; |
|||
var binding = new Binding |
|||
{ |
|||
Path = nameof(source.MustBeNonEmpty), |
|||
Mode = Avalonia.Data.BindingMode.TwoWay, |
|||
ValidationMethods = Avalonia.Data.ValidationMethods.None |
|||
Path = nameof(model.MustBePositive), |
|||
Mode = BindingMode.TwoWay, |
|||
EnableValidation = true, |
|||
}; |
|||
target.Bind(Control.TagProperty, binding); |
|||
|
|||
target.Tag = ""; |
|||
|
|||
Assert.True(target.ValidationStatus.IsValid); |
|||
control.Bind(TestControl.ValidationTestProperty, binding); |
|||
control.DataContext = model; |
|||
control.ValidationTest = -5; |
|||
Assert.Contains(control.Classes, x => x == ":invalid"); |
|||
} |
|||
|
|||
[Fact] |
|||
public void Enabled_Validation_Should_Trigger_Validation_Change_Styled() |
|||
public void Failed_Then_Passed_Validation_Should_Remove_Invalid_Pseudo_Class() |
|||
{ |
|||
var source = new Data { MustBeNonEmpty = "Test" }; |
|||
var target = new TextBlock { DataContext = source }; |
|||
var control = new TestControl(); |
|||
var model = new ValidationTestModel { MustBePositive = 1 }; |
|||
|
|||
var binding = new Binding |
|||
{ |
|||
Path = nameof(source.MustBeNonEmpty), |
|||
Mode = Avalonia.Data.BindingMode.TwoWay, |
|||
ValidationMethods = Avalonia.Data.ValidationMethods.All |
|||
Path = nameof(model.MustBePositive), |
|||
Mode = BindingMode.TwoWay, |
|||
EnableValidation = true, |
|||
}; |
|||
target.Bind(Control.TagProperty, binding); |
|||
|
|||
target.Tag = ""; |
|||
Assert.False(target.ValidationStatus.IsValid); |
|||
control.Bind(TestControl.ValidationTestProperty, binding); |
|||
control.DataContext = model; |
|||
|
|||
|
|||
control.ValidationTest = -5; |
|||
Assert.Contains(control.Classes, x => x == ":invalid"); |
|||
control.ValidationTest = 5; |
|||
Assert.DoesNotContain(control.Classes, x => x == ":invalid"); |
|||
} |
|||
|
|||
private class TestControl : Control |
|||
{ |
|||
public static readonly StyledProperty<int> ValidationTestProperty |
|||
= AvaloniaProperty.Register<TestControl, int>(nameof(ValidationTest), 1, defaultBindingMode: BindingMode.TwoWay); |
|||
|
|||
public int ValidationTest |
|||
{ |
|||
get |
|||
{ |
|||
return GetValue(ValidationTestProperty); |
|||
} |
|||
set |
|||
{ |
|||
SetValue(ValidationTestProperty, value); |
|||
} |
|||
} |
|||
|
|||
protected override void DataValidationChanged(AvaloniaProperty property, IValidationStatus status) |
|||
{ |
|||
if (property == ValidationTestProperty) |
|||
{ |
|||
UpdateValidationState(status); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private class ValidationTestModel |
|||
{ |
|||
private int mustBePositive; |
|||
|
|||
public int MustBePositive |
|||
{ |
|||
get { return mustBePositive; } |
|||
set |
|||
{ |
|||
if (value <= 0) |
|||
{ |
|||
throw new ArgumentOutOfRangeException(nameof(value)); |
|||
} |
|||
mustBePositive = value; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
Loading…
Reference in new issue