BindingExpressionTests.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Globalization;
  6. using System.Reactive.Linq;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. using Avalonia.Data;
  10. using Avalonia.Data.Converters;
  11. using Avalonia.Data.Core;
  12. using Avalonia.Markup.Parsers;
  13. using Avalonia.UnitTests;
  14. using Moq;
  15. using Xunit;
  16. namespace Avalonia.Base.UnitTests.Data.Core
  17. {
  18. public class BindingExpressionTests : IClassFixture<InvariantCultureFixture>
  19. {
  20. [Fact]
  21. public async Task Should_Get_Simple_Property_Value()
  22. {
  23. var data = new Class1 { StringValue = "foo" };
  24. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.StringValue), typeof(string));
  25. var result = await target.Take(1);
  26. Assert.Equal("foo", result);
  27. GC.KeepAlive(data);
  28. }
  29. [Fact]
  30. public void Should_Set_Simple_Property_Value()
  31. {
  32. var data = new Class1 { StringValue = "foo" };
  33. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.StringValue), typeof(string));
  34. target.OnNext("bar");
  35. Assert.Equal("bar", data.StringValue);
  36. GC.KeepAlive(data);
  37. }
  38. [Fact]
  39. public void Should_Set_Indexed_Value()
  40. {
  41. var data = new { Foo = new[] { "foo" } };
  42. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.Foo[0]), typeof(string));
  43. target.OnNext("bar");
  44. Assert.Equal("bar", data.Foo[0]);
  45. GC.KeepAlive(data);
  46. }
  47. [Fact]
  48. public async Task Should_Convert_Get_String_To_Double()
  49. {
  50. var data = new Class1 { StringValue = $"{5.6}" };
  51. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.StringValue), typeof(double));
  52. var result = await target.Take(1);
  53. Assert.Equal(5.6, result);
  54. GC.KeepAlive(data);
  55. }
  56. [Fact]
  57. public async Task Getting_Invalid_Double_String_Should_Return_BindingError()
  58. {
  59. var data = new Class1 { StringValue = "foo" };
  60. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.StringValue), typeof(double));
  61. var result = await target.Take(1);
  62. Assert.IsType<BindingNotification>(result);
  63. GC.KeepAlive(data);
  64. }
  65. [Fact]
  66. public async Task Should_Coerce_Get_Null_Double_String_To_UnsetValue()
  67. {
  68. var data = new Class1 { StringValue = null };
  69. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.StringValue), typeof(double));
  70. var result = await target.Take(1);
  71. Assert.Equal(AvaloniaProperty.UnsetValue, result);
  72. GC.KeepAlive(data);
  73. }
  74. [Fact]
  75. public void Should_Convert_Set_String_To_Double()
  76. {
  77. var data = new Class1 { StringValue = $"{5.6}" };
  78. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.StringValue), typeof(double));
  79. target.OnNext(6.7);
  80. Assert.Equal($"{6.7}", data.StringValue);
  81. GC.KeepAlive(data);
  82. }
  83. [Fact]
  84. public async Task Should_Convert_Get_Double_To_String()
  85. {
  86. var data = new Class1 { DoubleValue = 5.6 };
  87. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string));
  88. var result = await target.Take(1);
  89. Assert.Equal($"{5.6}", result);
  90. GC.KeepAlive(data);
  91. }
  92. [Fact]
  93. public void Should_Convert_Set_Double_To_String()
  94. {
  95. var data = new Class1 { DoubleValue = 5.6 };
  96. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string));
  97. target.OnNext($"{6.7}");
  98. Assert.Equal(6.7, data.DoubleValue);
  99. GC.KeepAlive(data);
  100. }
  101. [Fact]
  102. public async Task Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value()
  103. {
  104. var data = new Class1 { StringValue = "foo" };
  105. var target = new BindingExpression(
  106. ExpressionObserver.Create(data, o => o.StringValue),
  107. typeof(int),
  108. 42,
  109. DefaultValueConverter.Instance);
  110. var result = await target.Take(1);
  111. Assert.Equal(
  112. new BindingNotification(
  113. new InvalidCastException("'foo' is not a valid number."),
  114. BindingErrorType.Error,
  115. 42),
  116. result);
  117. GC.KeepAlive(data);
  118. }
  119. [Fact]
  120. public async Task Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value_With_Data_Validation()
  121. {
  122. var data = new Class1 { StringValue = "foo" };
  123. var target = new BindingExpression(
  124. ExpressionObserver.Create(data, o => o.StringValue, true),
  125. typeof(int),
  126. 42,
  127. DefaultValueConverter.Instance);
  128. var result = await target.Take(1);
  129. Assert.Equal(
  130. new BindingNotification(
  131. new InvalidCastException("'foo' is not a valid number."),
  132. BindingErrorType.Error,
  133. 42),
  134. result);
  135. GC.KeepAlive(data);
  136. }
  137. [Fact]
  138. public async Task Should_Return_BindingNotification_For_Invalid_FallbackValue()
  139. {
  140. var data = new Class1 { StringValue = "foo" };
  141. var target = new BindingExpression(
  142. ExpressionObserver.Create(data, o => o.StringValue),
  143. typeof(int),
  144. "bar",
  145. DefaultValueConverter.Instance);
  146. var result = await target.Take(1);
  147. Assert.Equal(
  148. new BindingNotification(
  149. new AggregateException(
  150. new InvalidCastException("'foo' is not a valid number."),
  151. new InvalidCastException("Could not convert FallbackValue 'bar' to 'System.Int32'")),
  152. BindingErrorType.Error),
  153. result);
  154. GC.KeepAlive(data);
  155. }
  156. [Fact]
  157. public async Task Should_Return_BindingNotification_For_Invalid_FallbackValue_With_Data_Validation()
  158. {
  159. var data = new Class1 { StringValue = "foo" };
  160. var target = new BindingExpression(
  161. ExpressionObserver.Create(data, o => o.StringValue, true),
  162. typeof(int),
  163. "bar",
  164. DefaultValueConverter.Instance);
  165. var result = await target.Take(1);
  166. Assert.Equal(
  167. new BindingNotification(
  168. new AggregateException(
  169. new InvalidCastException("'foo' is not a valid number."),
  170. new InvalidCastException("Could not convert FallbackValue 'bar' to 'System.Int32'")),
  171. BindingErrorType.Error),
  172. result);
  173. GC.KeepAlive(data);
  174. }
  175. [Fact]
  176. public void Setting_Invalid_Double_String_Should_Not_Change_Target()
  177. {
  178. var data = new Class1 { DoubleValue = 5.6 };
  179. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string));
  180. target.OnNext("foo");
  181. Assert.Equal(5.6, data.DoubleValue);
  182. GC.KeepAlive(data);
  183. }
  184. [Fact]
  185. public void Setting_Invalid_Double_String_Should_Use_FallbackValue()
  186. {
  187. var data = new Class1 { DoubleValue = 5.6 };
  188. var target = new BindingExpression(
  189. ExpressionObserver.Create(data, o => o.DoubleValue),
  190. typeof(string),
  191. "9.8",
  192. DefaultValueConverter.Instance);
  193. target.OnNext("foo");
  194. Assert.Equal(9.8, data.DoubleValue);
  195. GC.KeepAlive(data);
  196. }
  197. [Fact]
  198. public void Should_Coerce_Setting_Null_Double_To_Default_Value()
  199. {
  200. var data = new Class1 { DoubleValue = 5.6 };
  201. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string));
  202. target.OnNext(null);
  203. Assert.Equal(0, data.DoubleValue);
  204. GC.KeepAlive(data);
  205. }
  206. [Fact]
  207. public void Should_Coerce_Setting_UnsetValue_Double_To_Default_Value()
  208. {
  209. var data = new Class1 { DoubleValue = 5.6 };
  210. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string));
  211. target.OnNext(AvaloniaProperty.UnsetValue);
  212. Assert.Equal(0, data.DoubleValue);
  213. GC.KeepAlive(data);
  214. }
  215. [Fact]
  216. public void Should_Pass_ConverterParameter_To_Convert()
  217. {
  218. var data = new Class1 { DoubleValue = 5.6 };
  219. var converter = new Mock<IValueConverter>();
  220. var target = new BindingExpression(
  221. ExpressionObserver.Create(data, o => o.DoubleValue),
  222. typeof(string),
  223. converter.Object,
  224. converterParameter: "foo");
  225. target.Subscribe(_ => { });
  226. converter.Verify(x => x.Convert(5.6, typeof(string), "foo", CultureInfo.CurrentCulture));
  227. GC.KeepAlive(data);
  228. }
  229. [Fact]
  230. public void Should_Pass_ConverterParameter_To_ConvertBack()
  231. {
  232. var data = new Class1 { DoubleValue = 5.6 };
  233. var converter = new Mock<IValueConverter>();
  234. var target = new BindingExpression(
  235. ExpressionObserver.Create(data, o => o.DoubleValue),
  236. typeof(string),
  237. converter.Object,
  238. converterParameter: "foo");
  239. target.OnNext("bar");
  240. converter.Verify(x => x.ConvertBack("bar", typeof(double), "foo", CultureInfo.CurrentCulture));
  241. GC.KeepAlive(data);
  242. }
  243. [Fact]
  244. public void Should_Handle_DataValidation()
  245. {
  246. var data = new Class1 { DoubleValue = 5.6 };
  247. var converter = new Mock<IValueConverter>();
  248. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue, true), typeof(string));
  249. var result = new List<object>();
  250. target.Subscribe(x => result.Add(x));
  251. target.OnNext(1.2);
  252. target.OnNext($"{3.4}");
  253. target.OnNext("bar");
  254. Assert.Equal(
  255. new[]
  256. {
  257. new BindingNotification($"{5.6}"),
  258. new BindingNotification($"{1.2}"),
  259. new BindingNotification($"{3.4}"),
  260. new BindingNotification(
  261. new InvalidCastException("'bar' is not a valid number."),
  262. BindingErrorType.Error)
  263. },
  264. result);
  265. GC.KeepAlive(data);
  266. }
  267. [Fact]
  268. public void Second_Subscription_Should_Fire_Immediately()
  269. {
  270. var data = new Class1 { StringValue = "foo" };
  271. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.StringValue), typeof(string));
  272. object result = null;
  273. target.Subscribe();
  274. target.Subscribe(x => result = x);
  275. Assert.Equal("foo", result);
  276. GC.KeepAlive(data);
  277. }
  278. private class Class1 : NotifyingBase
  279. {
  280. private string _stringValue;
  281. private double _doubleValue;
  282. public string StringValue
  283. {
  284. get { return _stringValue; }
  285. set { _stringValue = value; RaisePropertyChanged(); }
  286. }
  287. public double DoubleValue
  288. {
  289. get { return _doubleValue; }
  290. set { _doubleValue = value; RaisePropertyChanged(); }
  291. }
  292. }
  293. }
  294. }