AnimatableTests.cs 16 KB

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