AvaloniaObjectTests_BatchUpdate.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Reactive;
  4. using System.Reactive.Disposables;
  5. using System.Reactive.Linq;
  6. using System.Text;
  7. using Avalonia.Data;
  8. using Xunit;
  9. namespace Avalonia.Base.UnitTests
  10. {
  11. public class AvaloniaObjectTests_BatchUpdate
  12. {
  13. [Fact]
  14. public void SetValue_Should_Not_Raise_Property_Changes_During_Batch_Update()
  15. {
  16. var target = new TestClass();
  17. var raised = new List<string>();
  18. target.GetObservable(TestClass.FooProperty).Skip(1).Subscribe(x => raised.Add(x));
  19. target.BeginBatchUpdate();
  20. target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue);
  21. Assert.Empty(raised);
  22. }
  23. [Fact]
  24. public void Binding_Should_Not_Raise_Property_Changes_During_Batch_Update()
  25. {
  26. var target = new TestClass();
  27. var observable = new TestObservable<string>("foo");
  28. var raised = new List<string>();
  29. target.GetObservable(TestClass.FooProperty).Skip(1).Subscribe(x => raised.Add(x));
  30. target.BeginBatchUpdate();
  31. target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue);
  32. Assert.Empty(raised);
  33. }
  34. [Fact]
  35. public void Binding_Completion_Should_Not_Raise_Property_Changes_During_Batch_Update()
  36. {
  37. var target = new TestClass();
  38. var observable = new TestObservable<string>("foo");
  39. var raised = new List<string>();
  40. target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue);
  41. target.GetObservable(TestClass.FooProperty).Skip(1).Subscribe(x => raised.Add(x));
  42. target.BeginBatchUpdate();
  43. observable.OnCompleted();
  44. Assert.Empty(raised);
  45. }
  46. [Fact]
  47. public void SetValue_Change_Should_Be_Raised_After_Batch_Update_1()
  48. {
  49. var target = new TestClass();
  50. var raised = new List<AvaloniaPropertyChangedEventArgs>();
  51. target.PropertyChanged += (s, e) => raised.Add(e);
  52. target.BeginBatchUpdate();
  53. target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue);
  54. target.EndBatchUpdate();
  55. Assert.Equal(1, raised.Count);
  56. Assert.Equal("foo", target.Foo);
  57. Assert.Null(raised[0].OldValue);
  58. Assert.Equal("foo", raised[0].NewValue);
  59. }
  60. [Fact]
  61. public void SetValue_Change_Should_Be_Raised_After_Batch_Update_2()
  62. {
  63. var target = new TestClass();
  64. var raised = new List<AvaloniaPropertyChangedEventArgs>();
  65. target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue);
  66. target.PropertyChanged += (s, e) => raised.Add(e);
  67. target.BeginBatchUpdate();
  68. target.SetValue(TestClass.FooProperty, "bar", BindingPriority.LocalValue);
  69. target.SetValue(TestClass.FooProperty, "baz", BindingPriority.LocalValue);
  70. target.EndBatchUpdate();
  71. Assert.Equal(1, raised.Count);
  72. Assert.Equal("baz", target.Foo);
  73. }
  74. [Fact]
  75. public void SetValue_Changes_Should_Be_Raised_In_Correct_Order_After_Batch_Update()
  76. {
  77. var target = new TestClass();
  78. var raised = new List<AvaloniaPropertyChangedEventArgs>();
  79. target.PropertyChanged += (s, e) => raised.Add(e);
  80. target.BeginBatchUpdate();
  81. target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue);
  82. target.SetValue(TestClass.BarProperty, "bar", BindingPriority.LocalValue);
  83. target.SetValue(TestClass.FooProperty, "baz", BindingPriority.LocalValue);
  84. target.EndBatchUpdate();
  85. Assert.Equal(2, raised.Count);
  86. Assert.Equal(TestClass.BarProperty, raised[0].Property);
  87. Assert.Equal(TestClass.FooProperty, raised[1].Property);
  88. Assert.Equal("baz", target.Foo);
  89. Assert.Equal("bar", target.Bar);
  90. }
  91. [Fact]
  92. public void SetValue_And_Binding_Changes_Should_Be_Raised_In_Correct_Order_After_Batch_Update_1()
  93. {
  94. var target = new TestClass();
  95. var observable = new TestObservable<string>("baz");
  96. var raised = new List<AvaloniaPropertyChangedEventArgs>();
  97. target.PropertyChanged += (s, e) => raised.Add(e);
  98. target.BeginBatchUpdate();
  99. target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue);
  100. target.SetValue(TestClass.BarProperty, "bar", BindingPriority.LocalValue);
  101. target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue);
  102. target.EndBatchUpdate();
  103. Assert.Equal(2, raised.Count);
  104. Assert.Equal(TestClass.BarProperty, raised[0].Property);
  105. Assert.Equal(TestClass.FooProperty, raised[1].Property);
  106. Assert.Equal("baz", target.Foo);
  107. Assert.Equal("bar", target.Bar);
  108. }
  109. [Fact]
  110. public void SetValue_And_Binding_Changes_Should_Be_Raised_In_Correct_Order_After_Batch_Update_2()
  111. {
  112. var target = new TestClass();
  113. var observable = new TestObservable<string>("foo");
  114. var raised = new List<AvaloniaPropertyChangedEventArgs>();
  115. target.PropertyChanged += (s, e) => raised.Add(e);
  116. target.BeginBatchUpdate();
  117. target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue);
  118. target.SetValue(TestClass.BarProperty, "bar", BindingPriority.LocalValue);
  119. target.SetValue(TestClass.FooProperty, "baz", BindingPriority.LocalValue);
  120. target.EndBatchUpdate();
  121. Assert.Equal(2, raised.Count);
  122. Assert.Equal(TestClass.BarProperty, raised[0].Property);
  123. Assert.Equal(TestClass.FooProperty, raised[1].Property);
  124. Assert.Equal("baz", target.Foo);
  125. Assert.Equal("bar", target.Bar);
  126. }
  127. [Fact]
  128. public void SetValue_And_Binding_Changes_Should_Be_Raised_In_Correct_Order_After_Batch_Update_3()
  129. {
  130. var target = new TestClass();
  131. var observable1 = new TestObservable<string>("foo");
  132. var observable2 = new TestObservable<string>("qux");
  133. var raised = new List<AvaloniaPropertyChangedEventArgs>();
  134. target.PropertyChanged += (s, e) => raised.Add(e);
  135. target.BeginBatchUpdate();
  136. target.Bind(TestClass.FooProperty, observable2, BindingPriority.LocalValue);
  137. target.Bind(TestClass.FooProperty, observable1, BindingPriority.LocalValue);
  138. target.SetValue(TestClass.BarProperty, "bar", BindingPriority.LocalValue);
  139. target.SetValue(TestClass.FooProperty, "baz", BindingPriority.LocalValue);
  140. target.EndBatchUpdate();
  141. Assert.Equal(2, raised.Count);
  142. Assert.Equal(TestClass.BarProperty, raised[0].Property);
  143. Assert.Equal(TestClass.FooProperty, raised[1].Property);
  144. Assert.Equal("baz", target.Foo);
  145. Assert.Equal("bar", target.Bar);
  146. }
  147. [Fact]
  148. public void Binding_Change_Should_Be_Raised_After_Batch_Update_1()
  149. {
  150. var target = new TestClass();
  151. var observable = new TestObservable<string>("foo");
  152. var raised = new List<AvaloniaPropertyChangedEventArgs>();
  153. target.PropertyChanged += (s, e) => raised.Add(e);
  154. target.BeginBatchUpdate();
  155. target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue);
  156. target.EndBatchUpdate();
  157. Assert.Equal(1, raised.Count);
  158. Assert.Equal("foo", target.Foo);
  159. Assert.Null(raised[0].OldValue);
  160. Assert.Equal("foo", raised[0].NewValue);
  161. }
  162. [Fact]
  163. public void Binding_Change_Should_Be_Raised_After_Batch_Update_2()
  164. {
  165. var target = new TestClass();
  166. var observable1 = new TestObservable<string>("bar");
  167. var observable2 = new TestObservable<string>("baz");
  168. var raised = new List<AvaloniaPropertyChangedEventArgs>();
  169. target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue);
  170. target.PropertyChanged += (s, e) => raised.Add(e);
  171. target.BeginBatchUpdate();
  172. target.Bind(TestClass.FooProperty, observable1, BindingPriority.LocalValue);
  173. target.Bind(TestClass.FooProperty, observable2, BindingPriority.LocalValue);
  174. target.EndBatchUpdate();
  175. Assert.Equal(1, raised.Count);
  176. Assert.Equal("baz", target.Foo);
  177. Assert.Equal("foo", raised[0].OldValue);
  178. Assert.Equal("baz", raised[0].NewValue);
  179. }
  180. [Fact]
  181. public void Binding_Completion_Should_Be_Raised_After_Batch_Update()
  182. {
  183. var target = new TestClass();
  184. var observable = new TestObservable<string>("foo");
  185. var raised = new List<AvaloniaPropertyChangedEventArgs>();
  186. target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue);
  187. target.PropertyChanged += (s, e) => raised.Add(e);
  188. target.BeginBatchUpdate();
  189. observable.OnCompleted();
  190. target.EndBatchUpdate();
  191. Assert.Equal(1, raised.Count);
  192. Assert.Null(target.Foo);
  193. Assert.Equal("foo", raised[0].OldValue);
  194. Assert.Null(raised[0].NewValue);
  195. Assert.Equal(BindingPriority.Unset, raised[0].Priority);
  196. }
  197. [Fact]
  198. public void ClearValue_Change_Should_Be_Raised_After_Batch_Update_1()
  199. {
  200. var target = new TestClass();
  201. var raised = new List<AvaloniaPropertyChangedEventArgs>();
  202. target.Foo = "foo";
  203. target.PropertyChanged += (s, e) => raised.Add(e);
  204. target.BeginBatchUpdate();
  205. target.ClearValue(TestClass.FooProperty);
  206. target.EndBatchUpdate();
  207. Assert.Equal(1, raised.Count);
  208. Assert.Null(target.Foo);
  209. Assert.Equal("foo", raised[0].OldValue);
  210. Assert.Null(raised[0].NewValue);
  211. Assert.Equal(BindingPriority.Unset, raised[0].Priority);
  212. }
  213. [Fact]
  214. public void Bindings_Should_Be_Subscribed_Before_Batch_Update()
  215. {
  216. var target = new TestClass();
  217. var observable1 = new TestObservable<string>("foo");
  218. var observable2 = new TestObservable<string>("bar");
  219. target.Bind(TestClass.FooProperty, observable1, BindingPriority.LocalValue);
  220. target.Bind(TestClass.FooProperty, observable2, BindingPriority.LocalValue);
  221. Assert.Equal(1, observable1.SubscribeCount);
  222. Assert.Equal(1, observable2.SubscribeCount);
  223. }
  224. [Fact]
  225. public void Non_Active_Binding_Should_Not_Be_Subscribed_Before_Batch_Update()
  226. {
  227. var target = new TestClass();
  228. var observable1 = new TestObservable<string>("foo");
  229. var observable2 = new TestObservable<string>("bar");
  230. target.Bind(TestClass.FooProperty, observable1, BindingPriority.LocalValue);
  231. target.Bind(TestClass.FooProperty, observable2, BindingPriority.Style);
  232. Assert.Equal(1, observable1.SubscribeCount);
  233. Assert.Equal(0, observable2.SubscribeCount);
  234. }
  235. [Fact]
  236. public void LocalValue_Bindings_Should_Be_Subscribed_During_Batch_Update()
  237. {
  238. var target = new TestClass();
  239. var observable1 = new TestObservable<string>("foo");
  240. var observable2 = new TestObservable<string>("bar");
  241. var raised = new List<AvaloniaPropertyChangedEventArgs>();
  242. target.PropertyChanged += (s, e) => raised.Add(e);
  243. // We need to subscribe to LocalValue bindings even if we've got a batch operation
  244. // in progress because otherwise we don't know whether the binding or a subsequent
  245. // SetValue with local priority will win. Notifications however shouldn't be sent.
  246. target.BeginBatchUpdate();
  247. target.Bind(TestClass.FooProperty, observable1, BindingPriority.LocalValue);
  248. target.Bind(TestClass.FooProperty, observable2, BindingPriority.LocalValue);
  249. Assert.Equal(1, observable1.SubscribeCount);
  250. Assert.Equal(1, observable2.SubscribeCount);
  251. Assert.Empty(raised);
  252. }
  253. [Fact]
  254. public void Style_Bindings_Should_Not_Be_Subscribed_During_Batch_Update()
  255. {
  256. var target = new TestClass();
  257. var observable1 = new TestObservable<string>("foo");
  258. var observable2 = new TestObservable<string>("bar");
  259. target.BeginBatchUpdate();
  260. target.Bind(TestClass.FooProperty, observable1, BindingPriority.Style);
  261. target.Bind(TestClass.FooProperty, observable2, BindingPriority.StyleTrigger);
  262. Assert.Equal(0, observable1.SubscribeCount);
  263. Assert.Equal(0, observable2.SubscribeCount);
  264. }
  265. [Fact]
  266. public void Active_Style_Binding_Should_Be_Subscribed_After_Batch_Uppdate_1()
  267. {
  268. var target = new TestClass();
  269. var observable1 = new TestObservable<string>("foo");
  270. var observable2 = new TestObservable<string>("bar");
  271. target.BeginBatchUpdate();
  272. target.Bind(TestClass.FooProperty, observable1, BindingPriority.Style);
  273. target.Bind(TestClass.FooProperty, observable2, BindingPriority.Style);
  274. target.EndBatchUpdate();
  275. Assert.Equal(0, observable1.SubscribeCount);
  276. Assert.Equal(1, observable2.SubscribeCount);
  277. }
  278. [Fact]
  279. public void Active_Style_Binding_Should_Be_Subscribed_After_Batch_Uppdate_2()
  280. {
  281. var target = new TestClass();
  282. var observable1 = new TestObservable<string>("foo");
  283. var observable2 = new TestObservable<string>("bar");
  284. target.BeginBatchUpdate();
  285. target.Bind(TestClass.FooProperty, observable1, BindingPriority.StyleTrigger);
  286. target.Bind(TestClass.FooProperty, observable2, BindingPriority.Style);
  287. target.EndBatchUpdate();
  288. Assert.Equal(1, observable1.SubscribeCount);
  289. Assert.Equal(0, observable2.SubscribeCount);
  290. }
  291. [Fact]
  292. public void Change_Can_Be_Triggered_By_Ending_Batch_Update_1()
  293. {
  294. var target = new TestClass();
  295. var raised = new List<AvaloniaPropertyChangedEventArgs>();
  296. target.PropertyChanged += (s, e) => raised.Add(e);
  297. target.BeginBatchUpdate();
  298. target.Foo = "foo";
  299. target.PropertyChanged += (s, e) =>
  300. {
  301. if (e.Property == TestClass.FooProperty && (string)e.NewValue == "foo")
  302. target.Bar = "bar";
  303. };
  304. target.EndBatchUpdate();
  305. Assert.Equal("foo", target.Foo);
  306. Assert.Equal("bar", target.Bar);
  307. Assert.Equal(2, raised.Count);
  308. Assert.Equal(TestClass.FooProperty, raised[0].Property);
  309. Assert.Equal(TestClass.BarProperty, raised[1].Property);
  310. }
  311. [Fact]
  312. public void Change_Can_Be_Triggered_By_Ending_Batch_Update_2()
  313. {
  314. var target = new TestClass();
  315. var raised = new List<AvaloniaPropertyChangedEventArgs>();
  316. target.PropertyChanged += (s, e) => raised.Add(e);
  317. target.BeginBatchUpdate();
  318. target.Foo = "foo";
  319. target.Bar = "baz";
  320. target.PropertyChanged += (s, e) =>
  321. {
  322. if (e.Property == TestClass.FooProperty && (string)e.NewValue == "foo")
  323. target.Bar = "bar";
  324. };
  325. target.EndBatchUpdate();
  326. Assert.Equal("foo", target.Foo);
  327. Assert.Equal("bar", target.Bar);
  328. Assert.Equal(2, raised.Count);
  329. }
  330. [Fact]
  331. public void Batch_Update_Can_Be_Triggered_By_Ending_Batch_Update()
  332. {
  333. var target = new TestClass();
  334. var raised = new List<AvaloniaPropertyChangedEventArgs>();
  335. target.PropertyChanged += (s, e) => raised.Add(e);
  336. target.BeginBatchUpdate();
  337. target.Foo = "foo";
  338. target.Bar = "baz";
  339. // Simulates the following scenario:
  340. // - A control is added to the logical tree
  341. // - A batch update is started to apply styles
  342. // - Ending the batch update triggers something which removes the control from the logical tree
  343. // - A new batch update is started to detach styles
  344. target.PropertyChanged += (s, e) =>
  345. {
  346. if (e.Property == TestClass.FooProperty && (string)e.NewValue == "foo")
  347. {
  348. target.BeginBatchUpdate();
  349. target.ClearValue(TestClass.FooProperty);
  350. target.ClearValue(TestClass.BarProperty);
  351. target.EndBatchUpdate();
  352. }
  353. };
  354. target.EndBatchUpdate();
  355. Assert.Null(target.Foo);
  356. Assert.Null(target.Bar);
  357. Assert.Equal(2, raised.Count);
  358. Assert.Equal(TestClass.FooProperty, raised[0].Property);
  359. Assert.Null(raised[0].OldValue);
  360. Assert.Equal("foo", raised[0].NewValue);
  361. Assert.Equal(TestClass.FooProperty, raised[1].Property);
  362. Assert.Equal("foo", raised[1].OldValue);
  363. Assert.Null(raised[1].NewValue);
  364. }
  365. public class TestClass : AvaloniaObject
  366. {
  367. public static readonly StyledProperty<string> FooProperty =
  368. AvaloniaProperty.Register<TestClass, string>(nameof(Foo));
  369. public static readonly StyledProperty<string> BarProperty =
  370. AvaloniaProperty.Register<TestClass, string>(nameof(Bar));
  371. public string Foo
  372. {
  373. get => GetValue(FooProperty);
  374. set => SetValue(FooProperty, value);
  375. }
  376. public string Bar
  377. {
  378. get => GetValue(BarProperty);
  379. set => SetValue(BarProperty, value);
  380. }
  381. }
  382. public class TestObservable<T> : ObservableBase<BindingValue<T>>
  383. {
  384. private readonly T _value;
  385. private IObserver<BindingValue<T>> _observer;
  386. public TestObservable(T value) => _value = value;
  387. public int SubscribeCount { get; private set; }
  388. public void OnCompleted() => _observer.OnCompleted();
  389. public void OnError(Exception e) => _observer.OnError(e);
  390. protected override IDisposable SubscribeCore(IObserver<BindingValue<T>> observer)
  391. {
  392. ++SubscribeCount;
  393. _observer = observer;
  394. observer.OnNext(_value);
  395. return Disposable.Empty;
  396. }
  397. }
  398. }
  399. }