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.Controls; |
||||
|
using Avalonia.Data; |
||||
using Avalonia.Markup.Xaml.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; |
using Xunit; |
||||
|
|
||||
namespace Avalonia.Markup.Xaml.UnitTests.Data |
namespace Avalonia.Markup.Xaml.UnitTests.Data |
||||
{ |
{ |
||||
public class BindingTests_Validation |
public class BindingTests_Validation |
||||
{ |
{ |
||||
public class Data : INotifyPropertyChanged |
[Fact] |
||||
|
public void Disabled_Validation_Should_Trigger_Validation_Change_On_Exception() |
||||
{ |
{ |
||||
private string mustbeNonEmpty; |
var source = new ValidationTestModel { MustBePositive = 5 }; |
||||
|
var target = new TestControl { DataContext = source }; |
||||
public string MustBeNonEmpty |
var binding = new Binding |
||||
{ |
{ |
||||
get { return mustbeNonEmpty; } |
Path = nameof(source.MustBePositive), |
||||
set |
Mode = BindingMode.TwoWay, |
||||
{ |
|
||||
if (string.IsNullOrEmpty(value)) |
|
||||
{ |
|
||||
throw new ArgumentException(nameof(value)); |
|
||||
} |
|
||||
mustbeNonEmpty = value; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public event PropertyChangedEventHandler PropertyChanged; |
// Even though EnableValidation = false, exception validation is enabled.
|
||||
|
EnableValidation = false, |
||||
|
}; |
||||
|
|
||||
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") |
target.Bind(TestControl.ValidationTestProperty, binding); |
||||
{ |
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); |
target.ValidationTest = -5; |
||||
} |
|
||||
|
Assert.False(target.ValidationStatus.IsValid); |
||||
} |
} |
||||
|
|
||||
[Fact] |
[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 source = new ValidationTestModel { MustBePositive = 5 }; |
||||
var target = new TextBlock { DataContext = source }; |
var target = new TestControl { DataContext = source }; |
||||
var binding = new Binding |
var binding = new Binding |
||||
{ |
{ |
||||
Path = nameof(source.MustBeNonEmpty), |
Path = nameof(source.MustBePositive), |
||||
Mode = Avalonia.Data.BindingMode.TwoWay, |
Mode = BindingMode.TwoWay, |
||||
ValidationMethods = Avalonia.Data.ValidationMethods.None |
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] |
[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 control = new TestControl(); |
||||
var target = new TextBlock { DataContext = source }; |
var model = new ValidationTestModel { MustBePositive = 1 }; |
||||
var binding = new Binding |
var binding = new Binding |
||||
{ |
{ |
||||
Path = nameof(source.MustBeNonEmpty), |
Path = nameof(model.MustBePositive), |
||||
Mode = Avalonia.Data.BindingMode.TwoWay, |
Mode = BindingMode.TwoWay, |
||||
ValidationMethods = Avalonia.Data.ValidationMethods.All |
EnableValidation = true, |
||||
}; |
}; |
||||
target.Bind(TextBlock.TextProperty, binding); |
|
||||
|
control.Bind(TestControl.ValidationTestProperty, binding); |
||||
target.Text = ""; |
control.DataContext = model; |
||||
Assert.False(target.ValidationStatus.IsValid); |
Assert.DoesNotContain(control.Classes, x => x == ":invalid"); |
||||
} |
} |
||||
|
|
||||
[Fact] |
[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 control = new TestControl(); |
||||
var target = new TextBlock { DataContext = source }; |
var model = new ValidationTestModel { MustBePositive = 1 }; |
||||
var binding = new Binding |
var binding = new Binding |
||||
{ |
{ |
||||
Path = nameof(source.MustBeNonEmpty), |
Path = nameof(model.MustBePositive), |
||||
Mode = Avalonia.Data.BindingMode.TwoWay, |
Mode = BindingMode.TwoWay, |
||||
ValidationMethods = Avalonia.Data.ValidationMethods.None |
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] |
[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 control = new TestControl(); |
||||
var target = new TextBlock { DataContext = source }; |
var model = new ValidationTestModel { MustBePositive = 1 }; |
||||
|
|
||||
var binding = new Binding |
var binding = new Binding |
||||
{ |
{ |
||||
Path = nameof(source.MustBeNonEmpty), |
Path = nameof(model.MustBePositive), |
||||
Mode = Avalonia.Data.BindingMode.TwoWay, |
Mode = BindingMode.TwoWay, |
||||
ValidationMethods = Avalonia.Data.ValidationMethods.All |
EnableValidation = true, |
||||
}; |
}; |
||||
target.Bind(Control.TagProperty, binding); |
|
||||
|
|
||||
target.Tag = ""; |
control.Bind(TestControl.ValidationTestProperty, binding); |
||||
Assert.False(target.ValidationStatus.IsValid); |
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