BindingExpressionTests.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  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. AvaloniaProperty.UnsetValue,
  110. DefaultValueConverter.Instance);
  111. var result = await target.Take(1);
  112. Assert.Equal(
  113. new BindingNotification(
  114. new InvalidCastException("'foo' is not a valid number."),
  115. BindingErrorType.Error,
  116. 42),
  117. result);
  118. GC.KeepAlive(data);
  119. }
  120. [Fact]
  121. public async Task Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value_With_Data_Validation()
  122. {
  123. var data = new Class1 { StringValue = "foo" };
  124. var target = new BindingExpression(
  125. ExpressionObserver.Create(data, o => o.StringValue, true),
  126. typeof(int),
  127. 42,
  128. AvaloniaProperty.UnsetValue,
  129. DefaultValueConverter.Instance);
  130. var result = await target.Take(1);
  131. Assert.Equal(
  132. new BindingNotification(
  133. new InvalidCastException("'foo' is not a valid number."),
  134. BindingErrorType.Error,
  135. 42),
  136. result);
  137. GC.KeepAlive(data);
  138. }
  139. [Fact]
  140. public async Task Should_Return_BindingNotification_For_Invalid_FallbackValue()
  141. {
  142. var data = new Class1 { StringValue = "foo" };
  143. var target = new BindingExpression(
  144. ExpressionObserver.Create(data, o => o.StringValue),
  145. typeof(int),
  146. "bar",
  147. AvaloniaProperty.UnsetValue,
  148. DefaultValueConverter.Instance);
  149. var result = await target.Take(1);
  150. Assert.Equal(
  151. new BindingNotification(
  152. new AggregateException(
  153. new InvalidCastException("'foo' is not a valid number."),
  154. new InvalidCastException("Could not convert FallbackValue 'bar' to 'System.Int32'")),
  155. BindingErrorType.Error),
  156. result);
  157. GC.KeepAlive(data);
  158. }
  159. [Fact]
  160. public async Task Should_Return_BindingNotification_For_Invalid_FallbackValue_With_Data_Validation()
  161. {
  162. var data = new Class1 { StringValue = "foo" };
  163. var target = new BindingExpression(
  164. ExpressionObserver.Create(data, o => o.StringValue, true),
  165. typeof(int),
  166. "bar",
  167. AvaloniaProperty.UnsetValue,
  168. DefaultValueConverter.Instance);
  169. var result = await target.Take(1);
  170. Assert.Equal(
  171. new BindingNotification(
  172. new AggregateException(
  173. new InvalidCastException("'foo' is not a valid number."),
  174. new InvalidCastException("Could not convert FallbackValue 'bar' to 'System.Int32'")),
  175. BindingErrorType.Error),
  176. result);
  177. GC.KeepAlive(data);
  178. }
  179. [Fact]
  180. public void Setting_Invalid_Double_String_Should_Not_Change_Target()
  181. {
  182. var data = new Class1 { DoubleValue = 5.6 };
  183. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string));
  184. target.OnNext("foo");
  185. Assert.Equal(5.6, data.DoubleValue);
  186. GC.KeepAlive(data);
  187. }
  188. [Fact]
  189. public void Setting_Invalid_Double_String_Should_Use_FallbackValue()
  190. {
  191. var data = new Class1 { DoubleValue = 5.6 };
  192. var target = new BindingExpression(
  193. ExpressionObserver.Create(data, o => o.DoubleValue),
  194. typeof(string),
  195. "9.8",
  196. AvaloniaProperty.UnsetValue,
  197. DefaultValueConverter.Instance);
  198. target.OnNext("foo");
  199. Assert.Equal(9.8, data.DoubleValue);
  200. GC.KeepAlive(data);
  201. }
  202. [Fact]
  203. public void Should_Coerce_Setting_Null_Double_To_Default_Value()
  204. {
  205. var data = new Class1 { DoubleValue = 5.6 };
  206. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string));
  207. target.OnNext(null);
  208. Assert.Equal(0, data.DoubleValue);
  209. GC.KeepAlive(data);
  210. }
  211. [Fact]
  212. public void Should_Coerce_Setting_UnsetValue_Double_To_Default_Value()
  213. {
  214. var data = new Class1 { DoubleValue = 5.6 };
  215. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string));
  216. target.OnNext(AvaloniaProperty.UnsetValue);
  217. Assert.Equal(0, data.DoubleValue);
  218. GC.KeepAlive(data);
  219. }
  220. [Fact]
  221. public void Should_Pass_ConverterParameter_To_Convert()
  222. {
  223. var data = new Class1 { DoubleValue = 5.6 };
  224. var converter = new Mock<IValueConverter>();
  225. var target = new BindingExpression(
  226. ExpressionObserver.Create(data, o => o.DoubleValue),
  227. typeof(string),
  228. converter.Object,
  229. converterParameter: "foo");
  230. target.Subscribe(_ => { });
  231. converter.Verify(x => x.Convert(5.6, typeof(string), "foo", CultureInfo.CurrentCulture));
  232. GC.KeepAlive(data);
  233. }
  234. [Fact]
  235. public void Should_Pass_ConverterParameter_To_ConvertBack()
  236. {
  237. var data = new Class1 { DoubleValue = 5.6 };
  238. var converter = new Mock<IValueConverter>();
  239. var target = new BindingExpression(
  240. ExpressionObserver.Create(data, o => o.DoubleValue),
  241. typeof(string),
  242. converter.Object,
  243. converterParameter: "foo");
  244. target.OnNext("bar");
  245. converter.Verify(x => x.ConvertBack("bar", typeof(double), "foo", CultureInfo.CurrentCulture));
  246. GC.KeepAlive(data);
  247. }
  248. [Fact]
  249. public void Should_Handle_DataValidation()
  250. {
  251. var data = new Class1 { DoubleValue = 5.6 };
  252. var converter = new Mock<IValueConverter>();
  253. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue, true), typeof(string));
  254. var result = new List<object>();
  255. target.Subscribe(x => result.Add(x));
  256. target.OnNext(1.2);
  257. target.OnNext($"{3.4}");
  258. target.OnNext("bar");
  259. Assert.Equal(
  260. new[]
  261. {
  262. new BindingNotification($"{5.6}"),
  263. new BindingNotification($"{1.2}"),
  264. new BindingNotification($"{3.4}"),
  265. new BindingNotification(
  266. new InvalidCastException("'bar' is not a valid number."),
  267. BindingErrorType.Error)
  268. },
  269. result);
  270. GC.KeepAlive(data);
  271. }
  272. [Fact]
  273. public void Second_Subscription_Should_Fire_Immediately()
  274. {
  275. var data = new Class1 { StringValue = "foo" };
  276. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.StringValue), typeof(string));
  277. object result = null;
  278. target.Subscribe();
  279. target.Subscribe(x => result = x);
  280. Assert.Equal("foo", result);
  281. GC.KeepAlive(data);
  282. }
  283. [Fact]
  284. public async Task Null_Value_Should_Use_TargetNullValue()
  285. {
  286. var data = new Class1 { StringValue = "foo" };
  287. var target = new BindingExpression(
  288. ExpressionObserver.Create(data, o => o.StringValue),
  289. typeof(string),
  290. AvaloniaProperty.UnsetValue,
  291. "bar",
  292. DefaultValueConverter.Instance);
  293. object result = null;
  294. target.Subscribe(x => result = x);
  295. Assert.Equal("foo", result);
  296. data.StringValue = null;
  297. Assert.Equal("bar", result);
  298. GC.KeepAlive(data);
  299. }
  300. private class Class1 : NotifyingBase
  301. {
  302. private string _stringValue;
  303. private double _doubleValue;
  304. public string StringValue
  305. {
  306. get { return _stringValue; }
  307. set { _stringValue = value; RaisePropertyChanged(); }
  308. }
  309. public double DoubleValue
  310. {
  311. get { return _doubleValue; }
  312. set { _doubleValue = value; RaisePropertyChanged(); }
  313. }
  314. }
  315. }
  316. }