BindingExpressionTests.cs 11 KB

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