BindingExpressionTests.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Reactive.Linq;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using Avalonia.Data;
  8. using Avalonia.Data.Converters;
  9. using Avalonia.Data.Core;
  10. using Avalonia.Markup.Parsers;
  11. using Avalonia.UnitTests;
  12. using Moq;
  13. using Xunit;
  14. namespace Avalonia.Base.UnitTests.Data.Core
  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(ExpressionObserver.Create(data, o => o.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(ExpressionObserver.Create(data, o => o.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(ExpressionObserver.Create(data, o => o.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(ExpressionObserver.Create(data, o => o.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(ExpressionObserver.Create(data, o => o.StringValue), typeof(double));
  59. var result = await target.Take(1);
  60. Assert.IsType<BindingNotification>(result);
  61. GC.KeepAlive(data);
  62. }
  63. [Fact]
  64. public void Should_Convert_Set_String_To_Double()
  65. {
  66. var data = new Class1 { StringValue = $"{5.6}" };
  67. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.StringValue), typeof(double));
  68. target.OnNext(6.7);
  69. Assert.Equal($"{6.7}", data.StringValue);
  70. GC.KeepAlive(data);
  71. }
  72. [Fact]
  73. public async Task Should_Convert_Get_Double_To_String()
  74. {
  75. var data = new Class1 { DoubleValue = 5.6 };
  76. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string));
  77. var result = await target.Take(1);
  78. Assert.Equal($"{5.6}", result);
  79. GC.KeepAlive(data);
  80. }
  81. [Fact]
  82. public void Should_Convert_Set_Double_To_String()
  83. {
  84. var data = new Class1 { DoubleValue = 5.6 };
  85. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string));
  86. target.OnNext($"{6.7}");
  87. Assert.Equal(6.7, data.DoubleValue);
  88. GC.KeepAlive(data);
  89. }
  90. [Fact]
  91. public async Task Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value()
  92. {
  93. var data = new Class1 { StringValue = "foo" };
  94. var target = new BindingExpression(
  95. ExpressionObserver.Create(data, o => o.StringValue),
  96. typeof(int),
  97. 42,
  98. AvaloniaProperty.UnsetValue,
  99. DefaultValueConverter.Instance,
  100. CultureInfo.InvariantCulture);
  101. var result = await target.Take(1);
  102. Assert.Equal(
  103. new BindingNotification(
  104. new InvalidCastException("'foo' is not a valid number."),
  105. BindingErrorType.Error,
  106. 42),
  107. result);
  108. GC.KeepAlive(data);
  109. }
  110. [Fact]
  111. public async Task Should_Return_BindingNotification_With_FallbackValue_For_NonConvertibe_Target_Value_With_Data_Validation()
  112. {
  113. var data = new Class1 { StringValue = "foo" };
  114. var target = new BindingExpression(
  115. ExpressionObserver.Create(data, o => o.StringValue, true),
  116. typeof(int),
  117. 42,
  118. AvaloniaProperty.UnsetValue,
  119. DefaultValueConverter.Instance,
  120. CultureInfo.InvariantCulture);
  121. var result = await target.Take(1);
  122. Assert.Equal(
  123. new BindingNotification(
  124. new InvalidCastException("'foo' is not a valid number."),
  125. BindingErrorType.Error,
  126. 42),
  127. result);
  128. GC.KeepAlive(data);
  129. }
  130. [Fact]
  131. public async Task Should_Return_BindingNotification_For_Invalid_FallbackValue()
  132. {
  133. var data = new Class1 { StringValue = "foo" };
  134. var target = new BindingExpression(
  135. ExpressionObserver.Create(data, o => o.StringValue),
  136. typeof(int),
  137. "bar",
  138. AvaloniaProperty.UnsetValue,
  139. DefaultValueConverter.Instance,
  140. CultureInfo.InvariantCulture);
  141. var result = await target.Take(1);
  142. Assert.Equal(
  143. new BindingNotification(
  144. new AggregateException(
  145. new InvalidCastException("'foo' is not a valid number."),
  146. new InvalidCastException("Could not convert FallbackValue 'bar' to 'System.Int32'")),
  147. BindingErrorType.Error),
  148. result);
  149. GC.KeepAlive(data);
  150. }
  151. [Fact]
  152. public async Task Should_Return_BindingNotification_For_Invalid_FallbackValue_With_Data_Validation()
  153. {
  154. var data = new Class1 { StringValue = "foo" };
  155. var target = new BindingExpression(
  156. ExpressionObserver.Create(data, o => o.StringValue, true),
  157. typeof(int),
  158. "bar",
  159. AvaloniaProperty.UnsetValue,
  160. DefaultValueConverter.Instance,
  161. CultureInfo.InvariantCulture);
  162. var result = await target.Take(1);
  163. Assert.Equal(
  164. new BindingNotification(
  165. new AggregateException(
  166. new InvalidCastException("'foo' is not a valid number."),
  167. new InvalidCastException("Could not convert FallbackValue 'bar' to 'System.Int32'")),
  168. BindingErrorType.Error),
  169. result);
  170. GC.KeepAlive(data);
  171. }
  172. [Fact]
  173. public void Setting_Invalid_Double_String_Should_Not_Change_Target()
  174. {
  175. var data = new Class1 { DoubleValue = 5.6 };
  176. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string));
  177. target.OnNext("foo");
  178. Assert.Equal(5.6, data.DoubleValue);
  179. GC.KeepAlive(data);
  180. }
  181. [Fact]
  182. public void Setting_Invalid_Double_String_Should_Use_FallbackValue()
  183. {
  184. var data = new Class1 { DoubleValue = 5.6 };
  185. var target = new BindingExpression(
  186. ExpressionObserver.Create(data, o => o.DoubleValue),
  187. typeof(string),
  188. "9.8",
  189. AvaloniaProperty.UnsetValue,
  190. DefaultValueConverter.Instance,
  191. CultureInfo.InvariantCulture);
  192. target.OnNext("foo");
  193. Assert.Equal(9.8, data.DoubleValue);
  194. GC.KeepAlive(data);
  195. }
  196. [Fact]
  197. public void Should_Coerce_Setting_UnsetValue_Double_To_Default_Value()
  198. {
  199. var data = new Class1 { DoubleValue = 5.6 };
  200. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue), typeof(string));
  201. target.OnNext(AvaloniaProperty.UnsetValue);
  202. Assert.Equal(0, data.DoubleValue);
  203. GC.KeepAlive(data);
  204. }
  205. [Fact]
  206. public void Should_Pass_ConverterParameter_To_Convert()
  207. {
  208. var data = new Class1 { DoubleValue = 5.6 };
  209. var converter = new Mock<IValueConverter>();
  210. var target = new BindingExpression(
  211. ExpressionObserver.Create(data, o => o.DoubleValue),
  212. typeof(string),
  213. converter.Object,
  214. CultureInfo.CurrentCulture,
  215. converterParameter: "foo");
  216. target.Subscribe(_ => { });
  217. converter.Verify(x => x.Convert(5.6, typeof(string), "foo", CultureInfo.CurrentCulture));
  218. GC.KeepAlive(data);
  219. }
  220. [Fact]
  221. public void Should_Pass_ConverterParameter_To_ConvertBack()
  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. CultureInfo.CurrentCulture,
  230. converterParameter: "foo");
  231. target.OnNext("bar");
  232. converter.Verify(x => x.ConvertBack("bar", typeof(double), "foo", CultureInfo.CurrentCulture));
  233. GC.KeepAlive(data);
  234. }
  235. [Fact]
  236. public void Should_Handle_DataValidation()
  237. {
  238. var data = new Class1 { DoubleValue = 5.6 };
  239. var converter = new Mock<IValueConverter>();
  240. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.DoubleValue, true), typeof(string));
  241. var result = new List<object>();
  242. target.Subscribe(x => result.Add(x));
  243. target.OnNext(1.2);
  244. target.OnNext($"{3.4}");
  245. target.OnNext("bar");
  246. Assert.Equal(
  247. new[]
  248. {
  249. new BindingNotification($"{5.6}"),
  250. new BindingNotification($"{1.2}"),
  251. new BindingNotification($"{3.4}"),
  252. new BindingNotification(
  253. new InvalidCastException("'bar' is not a valid number."),
  254. BindingErrorType.Error)
  255. },
  256. result);
  257. GC.KeepAlive(data);
  258. }
  259. [Fact]
  260. public void Second_Subscription_Should_Fire_Immediately()
  261. {
  262. var data = new Class1 { StringValue = "foo" };
  263. var target = new BindingExpression(ExpressionObserver.Create(data, o => o.StringValue), typeof(string));
  264. object result = null;
  265. target.Subscribe();
  266. target.Subscribe(x => result = x);
  267. Assert.Equal("foo", result);
  268. GC.KeepAlive(data);
  269. }
  270. [Fact]
  271. public void Null_Value_Should_Use_TargetNullValue()
  272. {
  273. var data = new Class1 { StringValue = "foo" };
  274. var target = new BindingExpression(
  275. ExpressionObserver.Create(data, o => o.StringValue),
  276. typeof(string),
  277. AvaloniaProperty.UnsetValue,
  278. "bar",
  279. DefaultValueConverter.Instance,
  280. CultureInfo.InvariantCulture);
  281. object result = null;
  282. target.Subscribe(x => result = x);
  283. Assert.Equal("foo", result);
  284. data.StringValue = null;
  285. Assert.Equal("bar", result);
  286. GC.KeepAlive(data);
  287. }
  288. private class Class1 : NotifyingBase
  289. {
  290. private string _stringValue;
  291. private double _doubleValue;
  292. public string StringValue
  293. {
  294. get { return _stringValue; }
  295. set { _stringValue = value; RaisePropertyChanged(); }
  296. }
  297. public double DoubleValue
  298. {
  299. get { return _doubleValue; }
  300. set { _doubleValue = value; RaisePropertyChanged(); }
  301. }
  302. }
  303. }
  304. }