ExpressionObserverTests_DataValidation.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Reactive.Linq;
  6. using Avalonia.Data;
  7. using Avalonia.Data.Core;
  8. using Avalonia.Markup.Parsers;
  9. using Avalonia.Threading;
  10. using Avalonia.UnitTests;
  11. using Xunit;
  12. namespace Avalonia.Base.UnitTests.Data.Core
  13. {
  14. public class ExpressionObserverTests_DataValidation : IClassFixture<InvariantCultureFixture>
  15. {
  16. ////[Fact]
  17. ////public void Doesnt_Send_DataValidationError_When_DataValidatation_Not_Enabled()
  18. ////{
  19. //// var data = new ExceptionTest { MustBePositive = 5 };
  20. //// var observer = ExpressionObserver.Create(data, o => o.MustBePositive, false);
  21. //// var validationMessageFound = false;
  22. //// observer.OfType<BindingNotification>()
  23. //// .Where(x => x.ErrorType == BindingErrorType.DataValidationError)
  24. //// .Subscribe(_ => validationMessageFound = true);
  25. //// observer.SetValue(-5);
  26. //// Assert.False(validationMessageFound);
  27. //// GC.KeepAlive(data);
  28. ////}
  29. ////[Fact]
  30. ////public void Exception_Validation_Sends_DataValidationError()
  31. ////{
  32. //// var data = new ExceptionTest { MustBePositive = 5 };
  33. //// var observer = ExpressionObserver.Create(data, o => o.MustBePositive, true);
  34. //// var validationMessageFound = false;
  35. //// observer.OfType<BindingNotification>()
  36. //// .Where(x => x.ErrorType == BindingErrorType.DataValidationError)
  37. //// .Subscribe(_ => validationMessageFound = true);
  38. //// observer.SetValue(-5);
  39. //// Assert.True(validationMessageFound);
  40. //// GC.KeepAlive(data);
  41. ////}
  42. [Fact]
  43. public void Indei_Validation_Does_Not_Subscribe_When_DataValidation_Not_Enabled()
  44. {
  45. var data = new IndeiTest { MustBePositive = 5 };
  46. var observer = ExpressionObserver.Create(data, o => o.MustBePositive, false);
  47. observer.Subscribe(_ => { });
  48. Assert.Equal(0, data.ErrorsChangedSubscriptionCount);
  49. }
  50. [Fact]
  51. public void Enabled_Indei_Validation_Subscribes()
  52. {
  53. var data = new IndeiTest { MustBePositive = 5 };
  54. var observer = ExpressionObserver.Create(data, o => o.MustBePositive, true);
  55. var sub = observer.Subscribe(_ => { });
  56. Assert.Equal(1, data.ErrorsChangedSubscriptionCount);
  57. sub.Dispose();
  58. // Forces WeakEvent compact
  59. Dispatcher.UIThread.RunJobs();
  60. Assert.Equal(0, data.ErrorsChangedSubscriptionCount);
  61. }
  62. [Fact]
  63. public void Validation_Plugins_Send_Correct_Notifications()
  64. {
  65. var data = new IndeiTest();
  66. var observer = ExpressionObserver.Create(data, o => o.MustBePositive, true);
  67. var result = new List<object>();
  68. var errmsg = string.Empty;
  69. try { typeof(IndeiTest).GetProperty(nameof(IndeiTest.MustBePositive)).SetValue(data, "foo"); }
  70. catch(Exception e) { errmsg = e.Message; }
  71. observer.Subscribe(x => result.Add(x));
  72. observer.SetValue(5);
  73. observer.SetValue(-5);
  74. observer.SetValue("foo");
  75. observer.SetValue(5);
  76. Assert.Equal(new[]
  77. {
  78. new BindingNotification(0),
  79. // Value is notified twice as ErrorsChanged is always called by IndeiTest.
  80. new BindingNotification(5),
  81. new BindingNotification(5),
  82. // Value is first signalled without an error as validation hasn't been updated.
  83. new BindingNotification(-5),
  84. new BindingNotification(new DataValidationException("Must be positive"), BindingErrorType.DataValidationError, -5),
  85. // Exception is thrown by trying to set value to "foo".
  86. new BindingNotification(
  87. new ArgumentException(errmsg),
  88. BindingErrorType.DataValidationError),
  89. // Value is set then validation is updated.
  90. new BindingNotification(new DataValidationException("Must be positive"), BindingErrorType.DataValidationError, 5),
  91. new BindingNotification(5),
  92. }, result);
  93. GC.KeepAlive(data);
  94. }
  95. [Fact]
  96. public void Doesnt_Subscribe_To_Indei_Of_Intermediate_Object_In_Chain()
  97. {
  98. var data = new Container
  99. {
  100. Inner = new IndeiTest()
  101. };
  102. var observer = ExpressionObserver.Create(data, o => o.Inner.MustBePositive, true);
  103. observer.Subscribe(_ => { });
  104. // We may want to change this but I've never seen an example of data validation on an
  105. // intermediate object in a chain so for the moment I'm not sure what the result of
  106. // validating such a thing should look like.
  107. Assert.Equal(0, data.ErrorsChangedSubscriptionCount);
  108. Assert.Equal(1, data.Inner.ErrorsChangedSubscriptionCount);
  109. }
  110. [Fact]
  111. public void Sends_Correct_Notifications_With_Property_Chain()
  112. {
  113. var container = new Container();
  114. var observer = ExpressionObserver.Create(container, o => o.Inner.MustBePositive, true);
  115. var result = new List<object>();
  116. observer.Subscribe(x => result.Add(x));
  117. Assert.Equal(new[]
  118. {
  119. new BindingNotification(
  120. new MarkupBindingChainException("Null value", "o => o.Inner.MustBePositive", "Inner"),
  121. BindingErrorType.Error,
  122. AvaloniaProperty.UnsetValue),
  123. }, result);
  124. GC.KeepAlive(container);
  125. }
  126. public class ExceptionTest : NotifyingBase
  127. {
  128. private int _mustBePositive;
  129. public int MustBePositive
  130. {
  131. get { return _mustBePositive; }
  132. set
  133. {
  134. if (value <= 0)
  135. {
  136. throw new ArgumentOutOfRangeException(nameof(value));
  137. }
  138. _mustBePositive = value;
  139. RaisePropertyChanged();
  140. }
  141. }
  142. }
  143. private class IndeiTest : IndeiBase
  144. {
  145. private int _mustBePositive;
  146. private Dictionary<string, IList<string>> _errors = new Dictionary<string, IList<string>>();
  147. public int MustBePositive
  148. {
  149. get { return _mustBePositive; }
  150. set
  151. {
  152. _mustBePositive = value;
  153. RaisePropertyChanged();
  154. if (value >= 0)
  155. {
  156. _errors.Remove(nameof(MustBePositive));
  157. RaiseErrorsChanged(nameof(MustBePositive));
  158. }
  159. else
  160. {
  161. _errors[nameof(MustBePositive)] = new[] { "Must be positive" };
  162. RaiseErrorsChanged(nameof(MustBePositive));
  163. }
  164. }
  165. }
  166. public override bool HasErrors => _mustBePositive >= 0;
  167. public override IEnumerable GetErrors(string propertyName)
  168. {
  169. IList<string> result;
  170. _errors.TryGetValue(propertyName, out result);
  171. return result;
  172. }
  173. }
  174. private class Container : IndeiBase
  175. {
  176. private IndeiTest _inner;
  177. public IndeiTest Inner
  178. {
  179. get { return _inner; }
  180. set { _inner = value; RaisePropertyChanged(); }
  181. }
  182. public override bool HasErrors => false;
  183. public override IEnumerable GetErrors(string propertyName) => null;
  184. }
  185. }
  186. }