/tests/Avalonia.Base.UnitTests/Data/Core/ExpressionObserverTests_Property.cs

https://gitlab.com/kush/Avalonia · C# · 643 lines · 502 code · 139 blank · 2 comment · 0 complexity · ab7d065aed04317d3f787d8ae703e23c MD5 · raw file

  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.Reactive;
  6. using System.Reactive.Linq;
  7. using System.Reactive.Subjects;
  8. using Microsoft.Reactive.Testing;
  9. using Avalonia.Data;
  10. using Avalonia.Data.Core;
  11. using Avalonia.UnitTests;
  12. using Xunit;
  13. using System.Threading.Tasks;
  14. using Avalonia.Markup.Parsers;
  15. namespace Avalonia.Base.UnitTests.Data.Core
  16. {
  17. public class ExpressionObserverTests_Property
  18. {
  19. [Fact]
  20. public async Task Should_Get_Simple_Property_Value()
  21. {
  22. var data = new { Foo = "foo" };
  23. var target = ExpressionObserver.Create(data, o => o.Foo);
  24. var result = await target.Take(1);
  25. Assert.Equal("foo", result);
  26. GC.KeepAlive(data);
  27. }
  28. [Fact]
  29. public void Should_Get_Simple_Property_Value_Type()
  30. {
  31. var data = new { Foo = "foo" };
  32. var target = ExpressionObserver.Create(data, o => o.Foo);
  33. target.Subscribe(_ => { });
  34. Assert.Equal(typeof(string), target.ResultType);
  35. GC.KeepAlive(data);
  36. }
  37. [Fact]
  38. public async Task Should_Get_Simple_Property_Value_Null()
  39. {
  40. var data = new { Foo = (string)null };
  41. var target = ExpressionObserver.Create(data, o => o.Foo);
  42. var result = await target.Take(1);
  43. Assert.Null(result);
  44. GC.KeepAlive(data);
  45. }
  46. [Fact]
  47. public async Task Should_Get_Simple_Property_From_Base_Class()
  48. {
  49. var data = new Class3 { Foo = "foo" };
  50. var target = ExpressionObserver.Create(data, o => o.Foo);
  51. var result = await target.Take(1);
  52. Assert.Equal("foo", result);
  53. GC.KeepAlive(data);
  54. }
  55. [Fact]
  56. public async Task Should_Return_BindingNotification_Error_For_Root_Null()
  57. {
  58. var target = ExpressionObserver.Create(default(Class3), o => o.Foo);
  59. var result = await target.Take(1);
  60. Assert.Equal(
  61. new BindingNotification(
  62. new MarkupBindingChainException("Null value", "o => o.Foo", string.Empty),
  63. BindingErrorType.Error,
  64. AvaloniaProperty.UnsetValue),
  65. result);
  66. }
  67. [Fact]
  68. public async Task Should_Return_BindingNotification_Error_For_Root_UnsetValue()
  69. {
  70. var target = ExpressionObserver.Create(AvaloniaProperty.UnsetValue, o => (o as Class3).Foo);
  71. var result = await target.Take(1);
  72. Assert.Equal(
  73. new BindingNotification(
  74. new MarkupBindingChainException("Null value", "o => (o As Class3).Foo", string.Empty),
  75. BindingErrorType.Error,
  76. AvaloniaProperty.UnsetValue),
  77. result);
  78. }
  79. [Fact]
  80. public async Task Should_Return_BindingNotification_Error_For_Observable_Root_Null()
  81. {
  82. var target = ExpressionObserver.Create(Observable.Return(default(Class3)), o => o.Foo);
  83. var result = await target.Take(1);
  84. Assert.Equal(
  85. new BindingNotification(
  86. new MarkupBindingChainException("Null value", "o => o.Foo", string.Empty),
  87. BindingErrorType.Error,
  88. AvaloniaProperty.UnsetValue),
  89. result);
  90. }
  91. [Fact]
  92. public async void Should_Return_BindingNotification_Error_For_Observable_Root_UnsetValue()
  93. {
  94. var target = ExpressionObserver.Create<object, string>(Observable.Return(AvaloniaProperty.UnsetValue), o => (o as Class3).Foo);
  95. var result = await target.Take(1);
  96. Assert.Equal(
  97. new BindingNotification(
  98. new MarkupBindingChainException("Null value", "o => (o As Class3).Foo", string.Empty),
  99. BindingErrorType.Error,
  100. AvaloniaProperty.UnsetValue),
  101. result);
  102. }
  103. [Fact]
  104. public async Task Should_Get_Simple_Property_Chain()
  105. {
  106. var data = new { Foo = new { Bar = new { Baz = "baz" } } };
  107. var target = ExpressionObserver.Create(data, o => o.Foo.Bar.Baz);
  108. var result = await target.Take(1);
  109. Assert.Equal("baz", result);
  110. GC.KeepAlive(data);
  111. }
  112. [Fact]
  113. public void Should_Get_Simple_Property_Chain_Type()
  114. {
  115. var data = new { Foo = new { Bar = new { Baz = "baz" } } };
  116. var target = ExpressionObserver.Create(data, o => o.Foo.Bar.Baz);
  117. target.Subscribe(_ => { });
  118. Assert.Equal(typeof(string), target.ResultType);
  119. GC.KeepAlive(data);
  120. }
  121. [Fact]
  122. public void Should_Return_BindingNotification_Error_For_Chain_With_Null_Value()
  123. {
  124. var data = new { Foo = default(Class1) };
  125. var target = ExpressionObserver.Create(data, o => o.Foo.Foo.Length);
  126. var result = new List<object>();
  127. target.Subscribe(x => result.Add(x));
  128. Assert.Equal(
  129. new[]
  130. {
  131. new BindingNotification(
  132. new MarkupBindingChainException("Null value", "o => o.Foo.Foo.Length", "Foo"),
  133. BindingErrorType.Error,
  134. AvaloniaProperty.UnsetValue),
  135. },
  136. result);
  137. GC.KeepAlive(data);
  138. }
  139. [Fact]
  140. public void Should_Track_Simple_Property_Value()
  141. {
  142. var data = new Class1 { Foo = "foo" };
  143. var target = ExpressionObserver.Create(data, o => o.Foo);
  144. var result = new List<object>();
  145. var sub = target.Subscribe(x => result.Add(x));
  146. data.Foo = "bar";
  147. Assert.Equal(new[] { "foo", "bar" }, result);
  148. sub.Dispose();
  149. Assert.Equal(0, data.PropertyChangedSubscriptionCount);
  150. GC.KeepAlive(data);
  151. }
  152. [Fact]
  153. public void Should_Trigger_PropertyChanged_On_Null_Or_Empty_String()
  154. {
  155. var data = new Class1 { Bar = "foo" };
  156. var target = ExpressionObserver.Create(data, o => o.Bar);
  157. var result = new List<object>();
  158. var sub = target.Subscribe(x => result.Add(x));
  159. Assert.Equal(new[] { "foo" }, result);
  160. data.Bar = "bar";
  161. Assert.Equal(new[] { "foo" }, result);
  162. data.RaisePropertyChanged(string.Empty);
  163. Assert.Equal(new[] { "foo", "bar" }, result);
  164. data.RaisePropertyChanged(null);
  165. Assert.Equal(new[] { "foo", "bar", "bar" }, result);
  166. sub.Dispose();
  167. Assert.Equal(0, data.PropertyChangedSubscriptionCount);
  168. GC.KeepAlive(data);
  169. }
  170. [Fact]
  171. public void Should_Track_End_Of_Property_Chain_Changing()
  172. {
  173. var data = new Class1 { Next = new Class2 { Bar = "bar" } };
  174. var target = ExpressionObserver.Create(data, o => (o.Next as Class2).Bar);
  175. var result = new List<object>();
  176. var sub = target.Subscribe(x => result.Add(x));
  177. ((Class2)data.Next).Bar = "baz";
  178. ((Class2)data.Next).Bar = null;
  179. Assert.Equal(new[] { "bar", "baz", null }, result);
  180. sub.Dispose();
  181. Assert.Equal(0, data.PropertyChangedSubscriptionCount);
  182. Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount);
  183. GC.KeepAlive(data);
  184. }
  185. [Fact]
  186. public void Should_Track_Property_Chain_Changing()
  187. {
  188. var data = new Class1 { Next = new Class2 { Bar = "bar" } };
  189. var target = ExpressionObserver.Create(data, o => (o.Next as Class2).Bar);
  190. var result = new List<object>();
  191. var sub = target.Subscribe(x => result.Add(x));
  192. var old = data.Next;
  193. data.Next = new Class2 { Bar = "baz" };
  194. data.Next = new Class2 { Bar = null };
  195. Assert.Equal(new[] { "bar", "baz", null }, result);
  196. sub.Dispose();
  197. Assert.Equal(0, data.PropertyChangedSubscriptionCount);
  198. Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount);
  199. Assert.Equal(0, old.PropertyChangedSubscriptionCount);
  200. GC.KeepAlive(data);
  201. }
  202. [Fact]
  203. public void Should_Track_Property_Chain_Breaking_With_Null_Then_Mending()
  204. {
  205. var data = new Class1
  206. {
  207. Next = new Class2
  208. {
  209. Next = new Class2
  210. {
  211. Bar = "bar"
  212. }
  213. }
  214. };
  215. var target = ExpressionObserver.Create(data, o => ((o.Next as Class2).Next as Class2).Bar);
  216. var result = new List<object>();
  217. var sub = target.Subscribe(x => result.Add(x));
  218. var old = data.Next;
  219. data.Next = new Class2 { Bar = "baz" };
  220. data.Next = old;
  221. Assert.Equal(
  222. new object[]
  223. {
  224. "bar",
  225. new BindingNotification(
  226. new MarkupBindingChainException("Null value", "o => ((o.Next As Class2).Next As Class2).Bar", "Next.Next"),
  227. BindingErrorType.Error,
  228. AvaloniaProperty.UnsetValue),
  229. "bar"
  230. },
  231. result);
  232. sub.Dispose();
  233. Assert.Equal(0, data.PropertyChangedSubscriptionCount);
  234. Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount);
  235. Assert.Equal(0, old.PropertyChangedSubscriptionCount);
  236. GC.KeepAlive(data);
  237. }
  238. [Fact]
  239. public void Should_Track_Property_Chain_Breaking_With_Missing_Member_Then_Mending()
  240. {
  241. var data = new Class1 { Next = new Class2 { Bar = "bar" } };
  242. var target = ExpressionObserver.Create(data, o => (o.Next as Class2).Bar);
  243. var result = new List<object>();
  244. var sub = target.Subscribe(x => result.Add(x));
  245. var old = data.Next;
  246. var breaking = new WithoutBar();
  247. data.Next = breaking;
  248. data.Next = new Class2 { Bar = "baz" };
  249. Assert.Equal(
  250. new object[]
  251. {
  252. "bar",
  253. new BindingNotification(
  254. new MissingMemberException("Could not find CLR property 'Bar' on 'Avalonia.Base.UnitTests.Data.Core.ExpressionObserverTests_Property+WithoutBar'"),
  255. BindingErrorType.Error),
  256. "baz",
  257. },
  258. result);
  259. sub.Dispose();
  260. Assert.Equal(0, data.PropertyChangedSubscriptionCount);
  261. Assert.Equal(0, data.Next.PropertyChangedSubscriptionCount);
  262. Assert.Equal(0, breaking.PropertyChangedSubscriptionCount);
  263. Assert.Equal(0, old.PropertyChangedSubscriptionCount);
  264. GC.KeepAlive(data);
  265. }
  266. [Fact]
  267. public void Empty_Expression_Should_Track_Root()
  268. {
  269. var data = new Class1 { Foo = "foo" };
  270. var update = new Subject<Unit>();
  271. var target = ExpressionObserver.Create(() => data.Foo, o => o, update);
  272. var result = new List<object>();
  273. target.Subscribe(x => result.Add(x));
  274. data.Foo = "bar";
  275. update.OnNext(Unit.Default);
  276. Assert.Equal(new[] { "foo", "bar" }, result);
  277. GC.KeepAlive(data);
  278. }
  279. [Fact]
  280. public void Should_Track_Property_Value_From_Observable_Root()
  281. {
  282. var scheduler = new TestScheduler();
  283. var source = scheduler.CreateColdObservable(
  284. OnNext(1, new Class1 { Foo = "foo" }),
  285. OnNext(2, new Class1 { Foo = "bar" }));
  286. var target = ExpressionObserver.Create(source, o => o.Foo);
  287. var result = new List<object>();
  288. using (target.Subscribe(x => result.Add(x)))
  289. {
  290. scheduler.Start();
  291. }
  292. Assert.Equal(new[] { "foo", "bar" }, result);
  293. Assert.All(source.Subscriptions, x => Assert.NotEqual(Subscription.Infinite, x.Unsubscribe));
  294. }
  295. [Fact]
  296. public void Subscribing_Multiple_Times_Should_Return_Values_To_All()
  297. {
  298. var data = new Class1 { Foo = "foo" };
  299. var target = ExpressionObserver.Create(data, o => o.Foo);
  300. var result1 = new List<object>();
  301. var result2 = new List<object>();
  302. var result3 = new List<object>();
  303. target.Subscribe(x => result1.Add(x));
  304. target.Subscribe(x => result2.Add(x));
  305. data.Foo = "bar";
  306. target.Subscribe(x => result3.Add(x));
  307. Assert.Equal(new[] { "foo", "bar" }, result1);
  308. Assert.Equal(new[] { "foo", "bar" }, result2);
  309. Assert.Equal(new[] { "bar" }, result3);
  310. GC.KeepAlive(data);
  311. }
  312. [Fact]
  313. public void Subscribing_Multiple_Times_Should_Only_Add_PropertyChanged_Handlers_Once()
  314. {
  315. var data = new Class1 { Foo = "foo" };
  316. var target = ExpressionObserver.Create(data, o => o.Foo);
  317. var sub1 = target.Subscribe(x => { });
  318. var sub2 = target.Subscribe(x => { });
  319. Assert.Equal(1, data.PropertyChangedSubscriptionCount);
  320. sub1.Dispose();
  321. sub2.Dispose();
  322. Assert.Equal(0, data.PropertyChangedSubscriptionCount);
  323. GC.KeepAlive(data);
  324. }
  325. [Fact]
  326. public void SetValue_Should_Set_Simple_Property_Value()
  327. {
  328. var data = new Class1 { Foo = "foo" };
  329. var target = ExpressionObserver.Create(data, o => o.Foo);
  330. using (target.Subscribe(_ => { }))
  331. {
  332. Assert.True(target.SetValue("bar"));
  333. }
  334. Assert.Equal("bar", data.Foo);
  335. GC.KeepAlive(data);
  336. }
  337. [Fact]
  338. public void SetValue_Should_Set_Property_At_The_End_Of_Chain()
  339. {
  340. var data = new Class1 { Next = new Class2 { Bar = "bar" } };
  341. var target = ExpressionObserver.Create(data, o => (o.Next as Class2).Bar);
  342. using (target.Subscribe(_ => { }))
  343. {
  344. Assert.True(target.SetValue("baz"));
  345. }
  346. Assert.Equal("baz", ((Class2)data.Next).Bar);
  347. GC.KeepAlive(data);
  348. }
  349. [Fact]
  350. public void SetValue_Should_Return_False_For_Missing_Property()
  351. {
  352. var data = new Class1 { Next = new WithoutBar() };
  353. var target = ExpressionObserver.Create(data, o => (o.Next as Class2).Bar);
  354. using (target.Subscribe(_ => { }))
  355. {
  356. Assert.False(target.SetValue("baz"));
  357. }
  358. GC.KeepAlive(data);
  359. }
  360. [Fact]
  361. public void SetValue_Should_Notify_New_Value_With_Inpc()
  362. {
  363. var data = new Class1();
  364. var target = ExpressionObserver.Create(data, o => o.Foo);
  365. var result = new List<object>();
  366. target.Subscribe(x => result.Add(x));
  367. target.SetValue("bar");
  368. Assert.Equal(new[] { null, "bar" }, result);
  369. GC.KeepAlive(data);
  370. }
  371. [Fact]
  372. public void SetValue_Should_Notify_New_Value_Without_Inpc()
  373. {
  374. var data = new Class1();
  375. var target = ExpressionObserver.Create(data, o => o.Bar);
  376. var result = new List<object>();
  377. target.Subscribe(x => result.Add(x));
  378. target.SetValue("bar");
  379. Assert.Equal(new[] { null, "bar" }, result);
  380. GC.KeepAlive(data);
  381. }
  382. [Fact]
  383. public void SetValue_Should_Return_False_For_Missing_Object()
  384. {
  385. var data = new Class1();
  386. var target = ExpressionObserver.Create(data, o => (o.Next as Class2).Bar);
  387. using (target.Subscribe(_ => { }))
  388. {
  389. Assert.False(target.SetValue("baz"));
  390. }
  391. GC.KeepAlive(data);
  392. }
  393. [Fact]
  394. public void Can_Replace_Root()
  395. {
  396. var first = new Class1 { Foo = "foo" };
  397. var second = new Class1 { Foo = "bar" };
  398. var root = first;
  399. var update = new Subject<Unit>();
  400. var target = ExpressionObserver.Create(() => root, o => o.Foo, update);
  401. var result = new List<object>();
  402. var sub = target.Subscribe(x => result.Add(x));
  403. root = second;
  404. update.OnNext(Unit.Default);
  405. root = null;
  406. update.OnNext(Unit.Default);
  407. Assert.Equal(
  408. new object[]
  409. {
  410. "foo",
  411. "bar",
  412. new BindingNotification(
  413. new MarkupBindingChainException("Null value", "o => o.Foo", string.Empty),
  414. BindingErrorType.Error,
  415. AvaloniaProperty.UnsetValue)
  416. },
  417. result);
  418. Assert.Equal(0, first.PropertyChangedSubscriptionCount);
  419. Assert.Equal(0, second.PropertyChangedSubscriptionCount);
  420. GC.KeepAlive(first);
  421. GC.KeepAlive(second);
  422. }
  423. [Fact]
  424. public void Should_Not_Keep_Source_Alive()
  425. {
  426. Func<Tuple<ExpressionObserver, WeakReference>> run = () =>
  427. {
  428. var source = new Class1 { Foo = "foo" };
  429. var target = ExpressionObserver.Create(source, o => o.Foo);
  430. return Tuple.Create(target, new WeakReference(source));
  431. };
  432. var result = run();
  433. result.Item1.Subscribe(x => { });
  434. GC.Collect();
  435. Assert.Null(result.Item2.Target);
  436. }
  437. private interface INext
  438. {
  439. int PropertyChangedSubscriptionCount { get; }
  440. }
  441. private class Class1 : NotifyingBase
  442. {
  443. private string _foo;
  444. private INext _next;
  445. public string Foo
  446. {
  447. get { return _foo; }
  448. set
  449. {
  450. _foo = value;
  451. RaisePropertyChanged(nameof(Foo));
  452. }
  453. }
  454. private string _bar;
  455. public string Bar
  456. {
  457. get { return _bar; }
  458. set { _bar = value; }
  459. }
  460. public INext Next
  461. {
  462. get { return _next; }
  463. set
  464. {
  465. _next = value;
  466. RaisePropertyChanged(nameof(Next));
  467. }
  468. }
  469. }
  470. private class Class2 : NotifyingBase, INext
  471. {
  472. private string _bar;
  473. private INext _next;
  474. public string Bar
  475. {
  476. get { return _bar; }
  477. set
  478. {
  479. _bar = value;
  480. RaisePropertyChanged(nameof(Bar));
  481. }
  482. }
  483. public INext Next
  484. {
  485. get { return _next; }
  486. set
  487. {
  488. _next = value;
  489. RaisePropertyChanged(nameof(Next));
  490. }
  491. }
  492. }
  493. private class Class3 : Class1
  494. {
  495. }
  496. private class WithoutBar : NotifyingBase, INext
  497. {
  498. }
  499. private Recorded<Notification<T>> OnNext<T>(long time, T value)
  500. {
  501. return new Recorded<Notification<T>>(time, Notification.CreateOnNext<T>(value));
  502. }
  503. }
  504. }