123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235 |
- // 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.Linq;
- using System.Reactive.Linq;
- using Avalonia.Data;
- using Avalonia.Data.Core;
- using Avalonia.UnitTests;
- using Xunit;
- namespace Avalonia.Base.UnitTests.Data.Core
- {
- public class ExpressionObserverTests_DataValidation : IClassFixture<InvariantCultureFixture>
- {
- [Fact]
- public void Doesnt_Send_DataValidationError_When_DataValidatation_Not_Enabled()
- {
- var data = new ExceptionTest { MustBePositive = 5 };
- var observer = new ExpressionObserver(data, nameof(data.MustBePositive), false);
- var validationMessageFound = false;
- observer.OfType<BindingNotification>()
- .Where(x => x.ErrorType == BindingErrorType.DataValidationError)
- .Subscribe(_ => validationMessageFound = true);
- observer.SetValue(-5);
- Assert.False(validationMessageFound);
- GC.KeepAlive(data);
- }
- [Fact]
- public void Exception_Validation_Sends_DataValidationError()
- {
- var data = new ExceptionTest { MustBePositive = 5 };
- var observer = new ExpressionObserver(data, nameof(data.MustBePositive), true);
- var validationMessageFound = false;
- observer.OfType<BindingNotification>()
- .Where(x => x.ErrorType == BindingErrorType.DataValidationError)
- .Subscribe(_ => validationMessageFound = true);
- observer.SetValue(-5);
- Assert.True(validationMessageFound);
- GC.KeepAlive(data);
- }
- [Fact]
- public void Indei_Validation_Does_Not_Subscribe_When_DataValidatation_Not_Enabled()
- {
- var data = new IndeiTest { MustBePositive = 5 };
- var observer = new ExpressionObserver(data, nameof(data.MustBePositive), false);
- observer.Subscribe(_ => { });
- Assert.Equal(0, data.ErrorsChangedSubscriptionCount);
- }
- [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.ErrorsChangedSubscriptionCount);
- sub.Dispose();
- Assert.Equal(0, data.ErrorsChangedSubscriptionCount);
- }
- [Fact]
- public void Validation_Plugins_Send_Correct_Notifications()
- {
- var data = new IndeiTest();
- var observer = new ExpressionObserver(data, nameof(data.MustBePositive), true);
- var result = new List<object>();
-
- var errmsg = string.Empty;
- try { typeof(IndeiTest).GetProperty(nameof(IndeiTest.MustBePositive)).SetValue(data, "foo"); }
- catch(Exception e) { errmsg = e.Message; }
- observer.Subscribe(x => result.Add(x));
- observer.SetValue(5);
- observer.SetValue(-5);
- observer.SetValue("foo");
- observer.SetValue(5);
- Assert.Equal(new[]
- {
- new BindingNotification(0),
- // Value is notified twice as ErrorsChanged is always called by IndeiTest.
- new BindingNotification(5),
- new BindingNotification(5),
- // Value is first signalled without an error as validation hasn't been updated.
- new BindingNotification(-5),
- new BindingNotification(new Exception("Must be positive"), BindingErrorType.DataValidationError, -5),
- // Exception is thrown by trying to set value to "foo".
- new BindingNotification(
- new ArgumentException(errmsg),
- BindingErrorType.DataValidationError),
- // Value is set then validation is updated.
- new BindingNotification(new Exception("Must be positive"), BindingErrorType.DataValidationError, 5),
- new BindingNotification(5),
- }, result);
- GC.KeepAlive(data);
- }
- [Fact]
- public void Doesnt_Subscribe_To_Indei_Of_Intermediate_Object_In_Chain()
- {
- var data = new Container
- {
- Inner = new IndeiTest()
- };
- var observer = new ExpressionObserver(
- data,
- $"{nameof(Container.Inner)}.{nameof(IndeiTest.MustBePositive)}",
- true);
- observer.Subscribe(_ => { });
- // We may want to change this but I've never seen an example of data validation on an
- // intermediate object in a chain so for the moment I'm not sure what the result of
- // validating such a thing should look like.
- Assert.Equal(0, data.ErrorsChangedSubscriptionCount);
- Assert.Equal(1, ((IndeiTest)data.Inner).ErrorsChangedSubscriptionCount);
- }
- [Fact]
- public void Sends_Correct_Notifications_With_Property_Chain()
- {
- var container = new Container();
- var inner = new IndeiTest();
- var observer = new ExpressionObserver(
- container,
- $"{nameof(Container.Inner)}.{nameof(IndeiTest.MustBePositive)}",
- true);
- var result = new List<object>();
- observer.Subscribe(x => result.Add(x));
- Assert.Equal(new[]
- {
- new BindingNotification(
- new MarkupBindingChainException("Null value", "Inner.MustBePositive", "Inner"),
- BindingErrorType.Error,
- AvaloniaProperty.UnsetValue),
- }, result);
- GC.KeepAlive(container);
- GC.KeepAlive(inner);
- }
- public class ExceptionTest : NotifyingBase
- {
- private int _mustBePositive;
- public int MustBePositive
- {
- get { return _mustBePositive; }
- set
- {
- if (value <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(value));
- }
- _mustBePositive = value;
- RaisePropertyChanged();
- }
- }
- }
- private class IndeiTest : IndeiBase
- {
- private int _mustBePositive;
- private Dictionary<string, IList<string>> _errors = new Dictionary<string, IList<string>>();
- public int MustBePositive
- {
- get { return _mustBePositive; }
- set
- {
- _mustBePositive = value;
- RaisePropertyChanged();
- if (value >= 0)
- {
- _errors.Remove(nameof(MustBePositive));
- RaiseErrorsChanged(nameof(MustBePositive));
- }
- else
- {
- _errors[nameof(MustBePositive)] = new[] { "Must be positive" };
- RaiseErrorsChanged(nameof(MustBePositive));
- }
- }
- }
- public override bool HasErrors => _mustBePositive >= 0;
- public override IEnumerable GetErrors(string propertyName)
- {
- IList<string> result;
- _errors.TryGetValue(propertyName, out result);
- return result;
- }
- }
- private class Container : IndeiBase
- {
- private object _inner;
- public object Inner
- {
- get { return _inner; }
- set { _inner = value; RaisePropertyChanged(); }
- }
- public override bool HasErrors => false;
- public override IEnumerable GetErrors(string propertyName) => null;
- }
- }
- }
|