ExpressionObserverTests_Property.cs 21 KB

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