1
0

TransitioningContentControlTests.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. using Avalonia.Animation;
  7. using Avalonia.Controls.Presenters;
  8. using Avalonia.Controls.Templates;
  9. using Avalonia.Headless;
  10. using Avalonia.Layout;
  11. using Avalonia.UnitTests;
  12. using Avalonia.VisualTree;
  13. using Xunit;
  14. #nullable enable
  15. namespace Avalonia.Controls.UnitTests
  16. {
  17. public class TransitioningContentControlTests : ScopedTestBase
  18. {
  19. [Fact]
  20. public void Transition_Should_Not_Be_Run_When_First_Shown()
  21. {
  22. using var app = Start();
  23. var (target, transition) = CreateTarget("foo");
  24. Assert.Equal(0, transition.StartCount);
  25. }
  26. [Fact]
  27. public void ContentPresenters2_Should_Initially_Be_Hidden()
  28. {
  29. using var app = Start();
  30. var (target, transition) = CreateTarget("foo");
  31. var presenter2 = GetContentPresenters2(target);
  32. Assert.False(presenter2.IsVisible);
  33. }
  34. [Fact]
  35. public void Transition_Should_Be_Run_On_Layout()
  36. {
  37. using var app = Start();
  38. var (target, transition) = CreateTarget("foo");
  39. target.Content = "bar";
  40. Assert.Equal(0, transition.StartCount);
  41. Layout(target);
  42. Assert.Equal(1, transition.StartCount);
  43. }
  44. [Fact]
  45. public void Control_Transition_Should_Be_Run_On_Layout()
  46. {
  47. using var app = Start();
  48. var (target, transition) = CreateTarget(new Button());
  49. target.Content = new Canvas();
  50. Assert.Equal(0, transition.StartCount);
  51. Layout(target);
  52. Assert.Equal(1, transition.StartCount);
  53. }
  54. [Fact]
  55. public void Control_Should_Connect_To_VisualTree_Once()
  56. {
  57. using var app = Start();
  58. var (target, transition) = CreateTarget(new Control());
  59. var control = new Control();
  60. int counter = 0;
  61. control.AttachedToVisualTree += (s,e) => counter++;
  62. target.Content = control;
  63. Layout(target);
  64. target.Content = new Control();
  65. Layout(target);
  66. Assert.Equal(1, counter);
  67. }
  68. [Fact]
  69. public void ContentPresenters2_Should_Be_Setup()
  70. {
  71. using var app = Start();
  72. var (target, transition) = CreateTarget("foo");
  73. var presenter1 = target.Presenter!;
  74. var presenter2 = GetContentPresenters2(target);
  75. target.Content = "bar";
  76. Layout(target);
  77. Assert.True(presenter2.IsVisible);
  78. Assert.Equal("foo", presenter1.Content);
  79. Assert.Equal("bar", presenter2.Content);
  80. }
  81. [Fact]
  82. public void Old_Presenter_Should_Be_Hidden_When_Transition_Completes()
  83. {
  84. using var app = Start();
  85. using var sync = UnitTestSynchronizationContext.Begin();
  86. var (target, transition) = CreateTarget("foo");
  87. var presenter1 = target.Presenter!;
  88. var presenter2 = GetContentPresenters2(target);
  89. target.Content = "bar";
  90. Layout(target);
  91. Assert.True(presenter1.IsVisible);
  92. Assert.True(presenter2.IsVisible);
  93. transition.Complete();
  94. sync.ExecutePostedCallbacks();
  95. Assert.True(presenter2.IsVisible);
  96. Assert.False(presenter1.IsVisible);
  97. target.Content = "foo";
  98. Layout(target);
  99. Assert.True(presenter1.IsVisible);
  100. Assert.True(presenter2.IsVisible);
  101. transition.Complete();
  102. sync.ExecutePostedCallbacks();
  103. Assert.True(presenter1.IsVisible);
  104. Assert.False(presenter2.IsVisible);
  105. }
  106. [Fact]
  107. public void TransitionCompleted_Should_Be_Raised_When_Content_Changes()
  108. {
  109. using var app = Start();
  110. using var sync = UnitTestSynchronizationContext.Begin();
  111. var (target, transition) = CreateTarget("foo");
  112. var completedTransitions = new List<TransitionCompletedEventArgs>();
  113. target.TransitionCompleted += (_, e) => completedTransitions.Add(e);
  114. target.Content = "bar";
  115. Layout(target);
  116. VerifyCompletedTransitions();
  117. transition.Complete();
  118. sync.ExecutePostedCallbacks();
  119. VerifyCompletedTransitions(new TransitionCompletedEventArgs("foo", "bar", true));
  120. target.Content = "foo";
  121. Layout(target);
  122. VerifyCompletedTransitions(new TransitionCompletedEventArgs("foo", "bar", true));
  123. transition.Complete();
  124. sync.ExecutePostedCallbacks();
  125. VerifyCompletedTransitions(new("foo", "bar", true), new("bar", "foo", true));
  126. void VerifyCompletedTransitions(params TransitionCompletedEventArgs[] expected)
  127. => Assert.Equal(expected, completedTransitions, TransitionCompletedEventArgsComparer.Instance);
  128. }
  129. [Fact]
  130. public void Transition_Should_Be_Canceled_If_Content_Changes_While_Running()
  131. {
  132. using var app = Start();
  133. using var sync = UnitTestSynchronizationContext.Begin();
  134. var (target, transition) = CreateTarget("foo");
  135. target.Content = "bar";
  136. Layout(target);
  137. target.Content = "baz";
  138. Assert.Equal(0, transition.CancelCount);
  139. Layout(target);
  140. Assert.Equal(1, transition.CancelCount);
  141. }
  142. [Fact]
  143. public void New_Transition_Should_Be_Started_If_Content_Changes_While_Running()
  144. {
  145. using var app = Start();
  146. using var sync = UnitTestSynchronizationContext.Begin();
  147. var (target, transition) = CreateTarget("foo");
  148. var presenter2 = GetContentPresenters2(target);
  149. target.Content = "bar";
  150. Layout(target);
  151. target.Content = "baz";
  152. var startedRaised = 0;
  153. transition.Started += (from, to, forward) =>
  154. {
  155. var fromPresenter = Assert.IsType<ContentPresenter>(from);
  156. var toPresenter = Assert.IsType<ContentPresenter>(to);
  157. Assert.Same(presenter2, fromPresenter);
  158. Assert.Same(target.Presenter, toPresenter);
  159. Assert.Equal("bar", fromPresenter.Content);
  160. Assert.Equal("baz", toPresenter.Content);
  161. Assert.True(forward);
  162. Assert.Equal(1, transition.CancelCount);
  163. ++startedRaised;
  164. };
  165. Layout(target);
  166. sync.ExecutePostedCallbacks();
  167. Assert.Equal(1, startedRaised);
  168. Assert.Equal("baz", target.Presenter!.Content);
  169. Assert.Equal("bar", presenter2.Content);
  170. }
  171. [Fact]
  172. public void TransitionCompleted_Should_Be_Raised_If_Content_Changes_While_Running()
  173. {
  174. using var app = Start();
  175. using var sync = UnitTestSynchronizationContext.Begin();
  176. var (target, _) = CreateTarget("foo");
  177. var completedTransitions = new List<TransitionCompletedEventArgs>();
  178. target.TransitionCompleted += (_, e) => completedTransitions.Add(e);
  179. target.Content = "bar";
  180. Layout(target);
  181. sync.ExecutePostedCallbacks();
  182. VerifyCompletedTransitions();
  183. target.Content = "baz";
  184. Layout(target);
  185. sync.ExecutePostedCallbacks();
  186. VerifyCompletedTransitions(new TransitionCompletedEventArgs("foo", "bar", false));
  187. void VerifyCompletedTransitions(params TransitionCompletedEventArgs[] expected)
  188. => Assert.Equal(expected, completedTransitions, TransitionCompletedEventArgsComparer.Instance);
  189. }
  190. [Theory]
  191. [InlineData(false)]
  192. [InlineData(true)]
  193. public void Transition_Should_Be_Reversed_If_Property_Is_Set(bool reversed)
  194. {
  195. using var app = Start();
  196. using var sync = UnitTestSynchronizationContext.Begin();
  197. var (target, transition) = CreateTarget("foo");
  198. var presenter2 = GetContentPresenters2(target);
  199. target.IsTransitionReversed = reversed;
  200. target.Content = "bar";
  201. var startedRaised = 0;
  202. transition.Started += (from, to, forward) =>
  203. {
  204. Assert.Equal(reversed, !forward);
  205. ++startedRaised;
  206. };
  207. Layout(target);
  208. sync.ExecutePostedCallbacks();
  209. Assert.Equal(1, startedRaised);
  210. Assert.Equal("foo", target.Presenter!.Content);
  211. Assert.Equal("bar", presenter2.Content);
  212. }
  213. [Fact]
  214. public void Logical_Children_Should_Not_Be_Duplicated()
  215. {
  216. using var app = Start();
  217. var (target, transition) = CreateTarget("");
  218. target.PageTransition = null;
  219. var childControl = new Control();
  220. target.Content = childControl;
  221. Assert.Equal(1, target.LogicalChildren.Count);
  222. Assert.Equal(target.LogicalChildren[0], childControl);
  223. }
  224. [Fact]
  225. public void First_Presenter_Should_Register_TCC_As_His_Host()
  226. {
  227. using var app = Start();
  228. var (target, transition) = CreateTarget("");
  229. target.PageTransition = null;
  230. var childControl = new Control();
  231. target.Presenter!.Content = childControl;
  232. Assert.Equal(1, target.LogicalChildren.Count);
  233. Assert.Equal(target.LogicalChildren[0], childControl);
  234. }
  235. [Fact]
  236. public void Old_Content_Should_Be_Null_When_New_Content_Is_Old_one()
  237. {
  238. using var app = Start();
  239. var (target, transition) = CreateTarget("");
  240. var presenter2 = GetContentPresenters2(target);
  241. target.PageTransition = null;
  242. var childControl = new Control();
  243. target.Presenter!.Content = childControl;
  244. const string fakePage1 = "fakePage1";
  245. const string fakePage2 = "fakePage2";
  246. target.Presenter!.Content = fakePage1;
  247. target.Presenter!.Content = fakePage2;
  248. target.Presenter!.Content = fakePage1;
  249. Assert.Equal(fakePage1, target.Presenter!.Content);
  250. Assert.Equal(null, presenter2.Content);
  251. }
  252. private static IDisposable Start()
  253. {
  254. return UnitTestApplication.Start(
  255. TestServices.MockThreadingInterface.With(
  256. fontManagerImpl: new HeadlessFontManagerStub(),
  257. renderInterface: new HeadlessPlatformRenderInterface(),
  258. textShaperImpl: new HeadlessTextShaperStub()));
  259. }
  260. private static (TransitioningContentControl, TestTransition) CreateTarget(object content)
  261. {
  262. var transition = new TestTransition();
  263. var target = new TransitioningContentControl
  264. {
  265. Content = content,
  266. PageTransition = transition,
  267. Template = CreateTemplate(),
  268. };
  269. var root = new TestRoot(target);
  270. root.LayoutManager.ExecuteInitialLayoutPass();
  271. return (target, transition);
  272. }
  273. private static IControlTemplate CreateTemplate()
  274. {
  275. return new FuncControlTemplate((x, ns) =>
  276. {
  277. return new Panel
  278. {
  279. Children =
  280. {
  281. new ContentPresenter
  282. {
  283. Name = "PART_ContentPresenter",
  284. },
  285. new ContentPresenter
  286. {
  287. Name = "PART_ContentPresenter2",
  288. },
  289. }
  290. };
  291. });
  292. }
  293. private static ContentPresenter GetContentPresenters2(TransitioningContentControl target)
  294. {
  295. return Assert.IsType<ContentPresenter>(target
  296. .GetTemplateChildren()
  297. .First(x => x.Name == "PART_ContentPresenter2"));
  298. }
  299. private void Layout(Control c)
  300. {
  301. (c.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass();
  302. }
  303. private class TestTransition : IPageTransition
  304. {
  305. private TaskCompletionSource? _tcs;
  306. public int StartCount { get; private set; }
  307. public int FinishCount { get; private set; }
  308. public int CancelCount { get; private set; }
  309. public event Action<Visual?, Visual?, bool>? Started;
  310. public async Task Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken)
  311. {
  312. ++StartCount;
  313. Started?.Invoke(from, to, forward);
  314. if (_tcs is not null)
  315. throw new InvalidOperationException("Transition already running");
  316. _tcs = new TaskCompletionSource();
  317. cancellationToken.Register(() => _tcs?.TrySetResult());
  318. await _tcs.Task;
  319. _tcs = null;
  320. if (!cancellationToken.IsCancellationRequested)
  321. ++FinishCount;
  322. else
  323. ++CancelCount;
  324. }
  325. public void Complete() => _tcs!.TrySetResult();
  326. }
  327. private sealed class TransitionCompletedEventArgsComparer : IEqualityComparer<TransitionCompletedEventArgs>
  328. {
  329. public static TransitionCompletedEventArgsComparer Instance { get; } = new();
  330. public bool Equals(TransitionCompletedEventArgs? x, TransitionCompletedEventArgs? y)
  331. {
  332. if (ReferenceEquals(x, y))
  333. return true;
  334. if (x is null || y is null)
  335. return false;
  336. return x.From == y.From && x.To == y.To && x.HasRunToCompletion == y.HasRunToCompletion;
  337. }
  338. public int GetHashCode(TransitionCompletedEventArgs obj)
  339. => HashCode.Combine(obj.From, obj.To, obj.HasRunToCompletion);
  340. }
  341. }
  342. }