AnimatableTests.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. using System;
  2. using Avalonia.Animation;
  3. using Avalonia.Controls;
  4. using Avalonia.Controls.Shapes;
  5. using Avalonia.Data;
  6. using Avalonia.Layout;
  7. using Avalonia.Media;
  8. using Avalonia.PropertyStore;
  9. using Avalonia.Styling;
  10. using Avalonia.UnitTests;
  11. using Moq;
  12. using Xunit;
  13. namespace Avalonia.Base.UnitTests.Animation
  14. {
  15. public class AnimatableTests
  16. {
  17. [Fact]
  18. public void Transition_Is_Not_Applied_When_Not_Attached_To_Visual_Tree()
  19. {
  20. var target = CreateTarget();
  21. var control = new Control
  22. {
  23. Transitions = new Transitions { target.Object },
  24. };
  25. control.Opacity = 0.5;
  26. target.Verify(x => x.Apply(
  27. control,
  28. It.IsAny<IClock>(),
  29. 1.0,
  30. 0.5),
  31. Times.Never);
  32. }
  33. [Fact]
  34. public void Transition_Is_Not_Applied_To_Initial_Style()
  35. {
  36. using (Start())
  37. {
  38. var target = CreateTarget();
  39. var control = new Control
  40. {
  41. Transitions = new Transitions { target.Object },
  42. };
  43. var root = new TestRoot
  44. {
  45. Styles =
  46. {
  47. new Style(x => x.OfType<Control>())
  48. {
  49. Setters =
  50. {
  51. new Setter(Visual.OpacityProperty, 0.8),
  52. }
  53. }
  54. }
  55. };
  56. root.Child = control;
  57. Assert.Equal(0.8, control.Opacity);
  58. target.Verify(x => x.Apply(
  59. It.IsAny<Control>(),
  60. It.IsAny<IClock>(),
  61. It.IsAny<object>(),
  62. It.IsAny<object>()),
  63. Times.Never);
  64. }
  65. }
  66. [Fact]
  67. public void Transition_Is_Applied_When_Local_Value_Changes()
  68. {
  69. using var app = Start();
  70. var target = CreateTarget();
  71. var control = CreateControl(target.Object);
  72. control.Opacity = 0.5;
  73. target.Verify(x => x.Apply(
  74. control,
  75. It.IsAny<IClock>(),
  76. 1.0,
  77. 0.5));
  78. }
  79. [Fact]
  80. public void Transition_Is_Not_Applied_When_Animated_Value_Changes()
  81. {
  82. var target = CreateTarget();
  83. var control = CreateControl(target.Object);
  84. control.SetValue(Visual.OpacityProperty, 0.5, BindingPriority.Animation);
  85. target.Verify(x => x.Apply(
  86. control,
  87. It.IsAny<IClock>(),
  88. 1.0,
  89. 0.5),
  90. Times.Never);
  91. }
  92. [Theory]
  93. [InlineData(null)] //null value
  94. [InlineData("stringValue")] //string value
  95. public void Invalid_Values_In_Animation_Should_Not_Crash_Animations(object invalidValue)
  96. {
  97. var keyframe1 = new KeyFrame()
  98. {
  99. Setters =
  100. {
  101. new Setter(Layoutable.WidthProperty, 1d),
  102. },
  103. KeyTime = TimeSpan.FromSeconds(0)
  104. };
  105. var keyframe2 = new KeyFrame()
  106. {
  107. Setters =
  108. {
  109. new Setter(Layoutable.WidthProperty, 2d),
  110. },
  111. KeyTime = TimeSpan.FromSeconds(2),
  112. };
  113. var keyframe3 = new KeyFrame()
  114. {
  115. Setters =
  116. {
  117. new Setter(Layoutable.WidthProperty, invalidValue),
  118. },
  119. KeyTime = TimeSpan.FromSeconds(3),
  120. };
  121. var animation = new Avalonia.Animation.Animation()
  122. {
  123. Duration = TimeSpan.FromSeconds(3),
  124. Children =
  125. {
  126. keyframe1,
  127. keyframe2,
  128. keyframe3
  129. },
  130. IterationCount = new IterationCount(5),
  131. PlaybackDirection = PlaybackDirection.Alternate,
  132. };
  133. var rect = new Rectangle()
  134. {
  135. Width = 11,
  136. };
  137. var originalValue = rect.Width;
  138. var clock = new TestClock();
  139. var animationRun = animation.RunAsync(rect, clock);
  140. clock.Step(TimeSpan.Zero);
  141. Assert.Equal(rect.Width, 1);
  142. clock.Step(TimeSpan.FromSeconds(2));
  143. Assert.Equal(rect.Width, 2);
  144. clock.Step(TimeSpan.FromSeconds(3));
  145. //here we have invalid value so value should be expected and set to initial original value
  146. Assert.Equal(rect.Width, originalValue);
  147. }
  148. [Fact]
  149. public void Transition_Is_Not_Applied_When_StyleTrigger_Changes_With_LocalValue_Present()
  150. {
  151. using var app = Start();
  152. var target = CreateTarget();
  153. var control = CreateControl(target.Object);
  154. control.SetValue(Visual.OpacityProperty, 0.5);
  155. target.Verify(x => x.Apply(
  156. control,
  157. It.IsAny<IClock>(),
  158. 1.0,
  159. 0.5));
  160. target.Invocations.Clear();
  161. control.SetValue(Visual.OpacityProperty, 0.8, BindingPriority.StyleTrigger);
  162. target.Verify(x => x.Apply(
  163. It.IsAny<Control>(),
  164. It.IsAny<IClock>(),
  165. It.IsAny<object>(),
  166. It.IsAny<object>()),
  167. Times.Never);
  168. }
  169. [Fact]
  170. public void Transition_Is_Disposed_When_Local_Value_Changes()
  171. {
  172. using var app = Start();
  173. var target = CreateTarget();
  174. var control = CreateControl(target.Object);
  175. var sub = new Mock<IDisposable>();
  176. target.Setup(x => x.Apply(control, It.IsAny<IClock>(), 1.0, 0.5)).Returns(sub.Object);
  177. control.Opacity = 0.5;
  178. sub.Invocations.Clear();
  179. control.Opacity = 0.4;
  180. sub.Verify(x => x.Dispose());
  181. }
  182. [Fact]
  183. public void New_Transition_Is_Applied_When_Local_Value_Changes()
  184. {
  185. using var app = Start();
  186. var target = CreateTarget();
  187. var control = CreateControl(target.Object);
  188. target.Setup(x => x.Property).Returns(Visual.OpacityProperty);
  189. target.Setup(x => x.Apply(control, It.IsAny<IClock>(), 1.0, 0.5))
  190. .Callback(() =>
  191. {
  192. control.SetValue(Visual.OpacityProperty, 0.9, BindingPriority.Animation);
  193. })
  194. .Returns(Mock.Of<IDisposable>());
  195. control.Opacity = 0.5;
  196. Assert.Equal(0.9, control.Opacity);
  197. target.Invocations.Clear();
  198. control.Opacity = 0.4;
  199. target.Verify(x => x.Apply(
  200. control,
  201. It.IsAny<IClock>(),
  202. 0.9,
  203. 0.4));
  204. }
  205. [Fact]
  206. public void Transition_Is_Not_Applied_When_Removed_From_Visual_Tree()
  207. {
  208. using var app = Start();
  209. var target = CreateTarget();
  210. var control = CreateControl(target.Object);
  211. control.Opacity = 0.5;
  212. target.Verify(x => x.Apply(
  213. control,
  214. It.IsAny<IClock>(),
  215. 1.0,
  216. 0.5));
  217. target.Invocations.Clear();
  218. var root = (TestRoot)control.Parent;
  219. root.Child = null;
  220. control.Opacity = 0.8;
  221. target.Verify(x => x.Apply(
  222. It.IsAny<Control>(),
  223. It.IsAny<IClock>(),
  224. It.IsAny<object>(),
  225. It.IsAny<object>()),
  226. Times.Never);
  227. }
  228. [Fact]
  229. public void Animation_Is_Cancelled_When_Transition_Removed()
  230. {
  231. using var app = Start();
  232. var target = CreateTarget();
  233. var control = CreateControl(target.Object);
  234. var sub = new Mock<IDisposable>();
  235. target.Setup(x => x.Apply(
  236. It.IsAny<Animatable>(),
  237. It.IsAny<IClock>(),
  238. It.IsAny<object>(),
  239. It.IsAny<object>())).Returns(sub.Object);
  240. control.Opacity = 0.5;
  241. control.Transitions.RemoveAt(0);
  242. sub.Verify(x => x.Dispose());
  243. }
  244. [Fact]
  245. public void Animation_Is_Cancelled_When_New_Style_Activates()
  246. {
  247. using (Start())
  248. {
  249. var target = CreateTarget();
  250. var control = CreateStyledControl(target.Object);
  251. var sub = new Mock<IDisposable>();
  252. target.Setup(x => x.Apply(
  253. control,
  254. It.IsAny<IClock>(),
  255. 1.0,
  256. 0.5)).Returns(sub.Object);
  257. control.Opacity = 0.5;
  258. target.Verify(x => x.Apply(
  259. control,
  260. It.IsAny<IClock>(),
  261. 1.0,
  262. 0.5),
  263. Times.Once);
  264. control.Classes.Add("foo");
  265. sub.Verify(x => x.Dispose());
  266. }
  267. }
  268. [Fact]
  269. public void Transition_From_Style_Trigger_Is_Applied()
  270. {
  271. using (Start())
  272. {
  273. var target = CreateTransition(Control.WidthProperty);
  274. var control = CreateStyledControl(transition2: target.Object);
  275. var sub = new Mock<IDisposable>();
  276. control.Classes.Add("foo");
  277. control.Width = 100;
  278. target.Verify(x => x.Apply(
  279. control,
  280. It.IsAny<IClock>(),
  281. double.NaN,
  282. 100.0),
  283. Times.Once);
  284. }
  285. }
  286. [Fact]
  287. public void Replacing_Transitions_During_Animation_Does_Not_Throw_KeyNotFound()
  288. {
  289. // Issue #4059
  290. using (Start())
  291. {
  292. Border target;
  293. var clock = new TestClock();
  294. var root = new TestRoot
  295. {
  296. Clock = clock,
  297. Styles =
  298. {
  299. new Style(x => x.OfType<Border>())
  300. {
  301. Setters =
  302. {
  303. new Setter(Border.TransitionsProperty,
  304. new Transitions
  305. {
  306. new DoubleTransition
  307. {
  308. Property = Border.OpacityProperty,
  309. Duration = TimeSpan.FromSeconds(1),
  310. },
  311. }),
  312. },
  313. },
  314. new Style(x => x.OfType<Border>().Class("foo"))
  315. {
  316. Setters =
  317. {
  318. new Setter(Border.TransitionsProperty,
  319. new Transitions
  320. {
  321. new DoubleTransition
  322. {
  323. Property = Border.OpacityProperty,
  324. Duration = TimeSpan.FromSeconds(1),
  325. },
  326. }),
  327. new Setter(Border.OpacityProperty, 0.0),
  328. },
  329. },
  330. },
  331. Child = target = new Border
  332. {
  333. Background = Brushes.Red,
  334. }
  335. };
  336. root.Measure(Size.Infinity);
  337. root.Arrange(new Rect(root.DesiredSize));
  338. target.Classes.Add("foo");
  339. clock.Step(TimeSpan.FromSeconds(0));
  340. clock.Step(TimeSpan.FromSeconds(0.5));
  341. Assert.Equal(0.5, target.Opacity);
  342. target.Classes.Remove("foo");
  343. }
  344. }
  345. [Fact]
  346. public void Transitions_Can_Be_Changed_To_Collection_That_Contains_The_Same_Transitions()
  347. {
  348. var target = CreateTarget();
  349. var control = CreateControl(target.Object);
  350. control.Transitions = new Transitions { target.Object };
  351. }
  352. [Fact]
  353. public void Transitions_Can_Re_Set_During_Styling()
  354. {
  355. var target = CreateTarget();
  356. var control = CreateControl(target.Object);
  357. // Assigning and then clearing Transitions ensures we have a transition state
  358. // collection created.
  359. control.ClearValue(Control.TransitionsProperty);
  360. control.GetValueStore().BeginStyling();
  361. // Setting opacity then Transitions means that we receive the Transitions change
  362. // after the Opacity change when EndStyling is called.
  363. var style = new Style
  364. {
  365. Setters =
  366. {
  367. new Setter(Control.OpacityProperty, 0.5),
  368. new Setter(Control.TransitionsProperty, new Transitions { target.Object }),
  369. }
  370. };
  371. StyleHelpers.TryAttach(style, control);
  372. // Which means that the transition state hasn't been initialized with the new
  373. // Transitions when the Opacity change notification gets raised here.
  374. control.GetValueStore().EndStyling();
  375. }
  376. [Fact]
  377. public void Transitions_Can_Be_Removed_While_Transition_In_Progress()
  378. {
  379. using var app = Start();
  380. var opacityTransition = new DoubleTransition
  381. {
  382. Property = Control.OpacityProperty,
  383. Duration = TimeSpan.FromSeconds(1),
  384. };
  385. var transitions = new Transitions { opacityTransition };
  386. var borderTheme = new ControlTheme(typeof(Border))
  387. {
  388. Setters =
  389. {
  390. new Setter(Control.TransitionsProperty, transitions),
  391. }
  392. };
  393. var clock = new TestClock();
  394. var root = new TestRoot
  395. {
  396. Clock = clock,
  397. Resources =
  398. {
  399. { typeof(Border), borderTheme },
  400. }
  401. };
  402. var border = new Border();
  403. root.Child = border;
  404. root.LayoutManager.ExecuteInitialLayoutPass();
  405. Assert.Same(transitions, border.Transitions);
  406. // First set property with a transition to a new value, and step the clock until
  407. // transition is complete.
  408. border.Opacity = 0;
  409. clock.Step(TimeSpan.FromSeconds(0));
  410. clock.Step(TimeSpan.FromSeconds(1));
  411. Assert.Equal(0, border.Opacity);
  412. // Now clear the property; a transition is now in progress but no local value is
  413. // set.
  414. border.ClearValue(Border.OpacityProperty);
  415. // Remove the transition by removing the control from the logical tree. This was
  416. // causing an exception.
  417. root.Child = null;
  418. }
  419. private static IDisposable Start()
  420. {
  421. var clock = new MockGlobalClock();
  422. var services = new TestServices(globalClock: clock);
  423. return UnitTestApplication.Start(services);
  424. }
  425. private static Mock<ITransition> CreateTarget()
  426. {
  427. return CreateTransition(Visual.OpacityProperty);
  428. }
  429. private static Control CreateControl(ITransition transition)
  430. {
  431. var control = new Control
  432. {
  433. Transitions = new Transitions { transition },
  434. };
  435. var root = new TestRoot(control);
  436. return control;
  437. }
  438. private static Control CreateStyledControl(
  439. ITransition transition1 = null,
  440. ITransition transition2 = null)
  441. {
  442. transition1 = transition1 ?? CreateTarget().Object;
  443. transition2 = transition2 ?? CreateTransition(Control.WidthProperty).Object;
  444. var control = new Control
  445. {
  446. Styles =
  447. {
  448. new Style(x => x.OfType<Control>())
  449. {
  450. Setters =
  451. {
  452. new Setter
  453. {
  454. Property = Control.TransitionsProperty,
  455. Value = new Transitions { transition1 },
  456. }
  457. }
  458. },
  459. new Style(x => x.OfType<Control>().Class("foo"))
  460. {
  461. Setters =
  462. {
  463. new Setter
  464. {
  465. Property = Control.TransitionsProperty,
  466. Value = new Transitions { transition2 },
  467. }
  468. }
  469. }
  470. }
  471. };
  472. var root = new TestRoot(control);
  473. return control;
  474. }
  475. private static Mock<ITransition> CreateTransition(AvaloniaProperty property)
  476. {
  477. var target = new Mock<ITransition>();
  478. var sub = new Mock<IDisposable>();
  479. target.Setup(x => x.Property).Returns(property);
  480. target.Setup(x => x.Apply(
  481. It.IsAny<Animatable>(),
  482. It.IsAny<IClock>(),
  483. It.IsAny<object>(),
  484. It.IsAny<object>())).Returns(sub.Object);
  485. return target;
  486. }
  487. }
  488. }