123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Threading;
- using System.Threading.Tasks;
- using Avalonia.Animation;
- using Avalonia.Controls.Presenters;
- using Avalonia.Controls.Templates;
- using Avalonia.Headless;
- using Avalonia.Layout;
- using Avalonia.UnitTests;
- using Avalonia.VisualTree;
- using Xunit;
- #nullable enable
- namespace Avalonia.Controls.UnitTests
- {
- public class TransitioningContentControlTests : ScopedTestBase
- {
- [Fact]
- public void Transition_Should_Not_Be_Run_When_First_Shown()
- {
- using var app = Start();
- var (target, transition) = CreateTarget("foo");
- Assert.Equal(0, transition.StartCount);
- }
- [Fact]
- public void ContentPresenters2_Should_Initially_Be_Hidden()
- {
- using var app = Start();
- var (target, transition) = CreateTarget("foo");
- var presenter2 = GetContentPresenters2(target);
- Assert.False(presenter2.IsVisible);
- }
- [Fact]
- public void Transition_Should_Be_Run_On_Layout()
- {
- using var app = Start();
- var (target, transition) = CreateTarget("foo");
- target.Content = "bar";
- Assert.Equal(0, transition.StartCount);
- Layout(target);
- Assert.Equal(1, transition.StartCount);
- }
- [Fact]
- public void Control_Transition_Should_Be_Run_On_Layout()
- {
- using var app = Start();
- var (target, transition) = CreateTarget(new Button());
- target.Content = new Canvas();
- Assert.Equal(0, transition.StartCount);
- Layout(target);
- Assert.Equal(1, transition.StartCount);
- }
- [Fact]
- public void Control_Should_Connect_To_VisualTree_Once()
- {
- using var app = Start();
- var (target, transition) = CreateTarget(new Control());
- var control = new Control();
- int counter = 0;
- control.AttachedToVisualTree += (s,e) => counter++;
- target.Content = control;
- Layout(target);
- target.Content = new Control();
- Layout(target);
-
- Assert.Equal(1, counter);
- }
- [Fact]
- public void ContentPresenters2_Should_Be_Setup()
- {
- using var app = Start();
- var (target, transition) = CreateTarget("foo");
- var presenter1 = target.Presenter!;
- var presenter2 = GetContentPresenters2(target);
- target.Content = "bar";
- Layout(target);
- Assert.True(presenter2.IsVisible);
- Assert.Equal("foo", presenter1.Content);
- Assert.Equal("bar", presenter2.Content);
- }
- [Fact]
- public void Old_Presenter_Should_Be_Hidden_When_Transition_Completes()
- {
- using var app = Start();
- using var sync = UnitTestSynchronizationContext.Begin();
- var (target, transition) = CreateTarget("foo");
- var presenter1 = target.Presenter!;
- var presenter2 = GetContentPresenters2(target);
- target.Content = "bar";
- Layout(target);
- Assert.True(presenter1.IsVisible);
- Assert.True(presenter2.IsVisible);
- transition.Complete();
- sync.ExecutePostedCallbacks();
- Assert.True(presenter2.IsVisible);
- Assert.False(presenter1.IsVisible);
- target.Content = "foo";
- Layout(target);
- Assert.True(presenter1.IsVisible);
- Assert.True(presenter2.IsVisible);
- transition.Complete();
- sync.ExecutePostedCallbacks();
- Assert.True(presenter1.IsVisible);
- Assert.False(presenter2.IsVisible);
- }
- [Fact]
- public void TransitionCompleted_Should_Be_Raised_When_Content_Changes()
- {
- using var app = Start();
- using var sync = UnitTestSynchronizationContext.Begin();
- var (target, transition) = CreateTarget("foo");
- var completedTransitions = new List<TransitionCompletedEventArgs>();
- target.TransitionCompleted += (_, e) => completedTransitions.Add(e);
- target.Content = "bar";
- Layout(target);
- VerifyCompletedTransitions();
- transition.Complete();
- sync.ExecutePostedCallbacks();
- VerifyCompletedTransitions(new TransitionCompletedEventArgs("foo", "bar", true));
- target.Content = "foo";
- Layout(target);
- VerifyCompletedTransitions(new TransitionCompletedEventArgs("foo", "bar", true));
- transition.Complete();
- sync.ExecutePostedCallbacks();
- VerifyCompletedTransitions(new("foo", "bar", true), new("bar", "foo", true));
- void VerifyCompletedTransitions(params TransitionCompletedEventArgs[] expected)
- => Assert.Equal(expected, completedTransitions, TransitionCompletedEventArgsComparer.Instance);
- }
- [Fact]
- public void Transition_Should_Be_Canceled_If_Content_Changes_While_Running()
- {
- using var app = Start();
- using var sync = UnitTestSynchronizationContext.Begin();
- var (target, transition) = CreateTarget("foo");
- target.Content = "bar";
- Layout(target);
- target.Content = "baz";
- Assert.Equal(0, transition.CancelCount);
- Layout(target);
- Assert.Equal(1, transition.CancelCount);
- }
- [Fact]
- public void New_Transition_Should_Be_Started_If_Content_Changes_While_Running()
- {
- using var app = Start();
- using var sync = UnitTestSynchronizationContext.Begin();
- var (target, transition) = CreateTarget("foo");
- var presenter2 = GetContentPresenters2(target);
- target.Content = "bar";
- Layout(target);
- target.Content = "baz";
- var startedRaised = 0;
- transition.Started += (from, to, forward) =>
- {
- var fromPresenter = Assert.IsType<ContentPresenter>(from);
- var toPresenter = Assert.IsType<ContentPresenter>(to);
- Assert.Same(presenter2, fromPresenter);
- Assert.Same(target.Presenter, toPresenter);
- Assert.Equal("bar", fromPresenter.Content);
- Assert.Equal("baz", toPresenter.Content);
- Assert.True(forward);
- Assert.Equal(1, transition.CancelCount);
- ++startedRaised;
- };
- Layout(target);
- sync.ExecutePostedCallbacks();
- Assert.Equal(1, startedRaised);
- Assert.Equal("baz", target.Presenter!.Content);
- Assert.Equal("bar", presenter2.Content);
- }
- [Fact]
- public void TransitionCompleted_Should_Be_Raised_If_Content_Changes_While_Running()
- {
- using var app = Start();
- using var sync = UnitTestSynchronizationContext.Begin();
- var (target, _) = CreateTarget("foo");
- var completedTransitions = new List<TransitionCompletedEventArgs>();
- target.TransitionCompleted += (_, e) => completedTransitions.Add(e);
- target.Content = "bar";
- Layout(target);
- sync.ExecutePostedCallbacks();
- VerifyCompletedTransitions();
- target.Content = "baz";
- Layout(target);
- sync.ExecutePostedCallbacks();
- VerifyCompletedTransitions(new TransitionCompletedEventArgs("foo", "bar", false));
- void VerifyCompletedTransitions(params TransitionCompletedEventArgs[] expected)
- => Assert.Equal(expected, completedTransitions, TransitionCompletedEventArgsComparer.Instance);
- }
- [Theory]
- [InlineData(false)]
- [InlineData(true)]
- public void Transition_Should_Be_Reversed_If_Property_Is_Set(bool reversed)
- {
- using var app = Start();
- using var sync = UnitTestSynchronizationContext.Begin();
- var (target, transition) = CreateTarget("foo");
- var presenter2 = GetContentPresenters2(target);
- target.IsTransitionReversed = reversed;
- target.Content = "bar";
- var startedRaised = 0;
- transition.Started += (from, to, forward) =>
- {
- Assert.Equal(reversed, !forward);
- ++startedRaised;
- };
- Layout(target);
- sync.ExecutePostedCallbacks();
- Assert.Equal(1, startedRaised);
- Assert.Equal("foo", target.Presenter!.Content);
- Assert.Equal("bar", presenter2.Content);
- }
- [Fact]
- public void Logical_Children_Should_Not_Be_Duplicated()
- {
- using var app = Start();
- var (target, transition) = CreateTarget("");
- target.PageTransition = null;
- var childControl = new Control();
- target.Content = childControl;
- Assert.Equal(1, target.LogicalChildren.Count);
- Assert.Equal(target.LogicalChildren[0], childControl);
- }
- [Fact]
- public void First_Presenter_Should_Register_TCC_As_His_Host()
- {
- using var app = Start();
- var (target, transition) = CreateTarget("");
- target.PageTransition = null;
- var childControl = new Control();
- target.Presenter!.Content = childControl;
- Assert.Equal(1, target.LogicalChildren.Count);
- Assert.Equal(target.LogicalChildren[0], childControl);
- }
- [Fact]
- public void Old_Content_Should_Be_Null_When_New_Content_Is_Old_one()
- {
- using var app = Start();
- var (target, transition) = CreateTarget("");
- var presenter2 = GetContentPresenters2(target);
- target.PageTransition = null;
- var childControl = new Control();
- target.Presenter!.Content = childControl;
- const string fakePage1 = "fakePage1";
- const string fakePage2 = "fakePage2";
- target.Presenter!.Content = fakePage1;
- target.Presenter!.Content = fakePage2;
- target.Presenter!.Content = fakePage1;
- Assert.Equal(fakePage1, target.Presenter!.Content);
- Assert.Equal(null, presenter2.Content);
- }
- private static IDisposable Start()
- {
- return UnitTestApplication.Start(
- TestServices.MockThreadingInterface.With(
- fontManagerImpl: new HeadlessFontManagerStub(),
- renderInterface: new HeadlessPlatformRenderInterface(),
- textShaperImpl: new HeadlessTextShaperStub()));
- }
- private static (TransitioningContentControl, TestTransition) CreateTarget(object content)
- {
- var transition = new TestTransition();
- var target = new TransitioningContentControl
- {
- Content = content,
- PageTransition = transition,
- Template = CreateTemplate(),
- };
- var root = new TestRoot(target);
- root.LayoutManager.ExecuteInitialLayoutPass();
- return (target, transition);
- }
- private static IControlTemplate CreateTemplate()
- {
- return new FuncControlTemplate((x, ns) =>
- {
- return new Panel
- {
- Children =
- {
- new ContentPresenter
- {
- Name = "PART_ContentPresenter",
- },
- new ContentPresenter
- {
- Name = "PART_ContentPresenter2",
- },
- }
- };
- });
- }
- private static ContentPresenter GetContentPresenters2(TransitioningContentControl target)
- {
- return Assert.IsType<ContentPresenter>(target
- .GetTemplateChildren()
- .First(x => x.Name == "PART_ContentPresenter2"));
- }
- private void Layout(Control c)
- {
- (c.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass();
- }
- private class TestTransition : IPageTransition
- {
- private TaskCompletionSource? _tcs;
- public int StartCount { get; private set; }
- public int FinishCount { get; private set; }
- public int CancelCount { get; private set; }
- public event Action<Visual?, Visual?, bool>? Started;
- public async Task Start(Visual? from, Visual? to, bool forward, CancellationToken cancellationToken)
- {
- ++StartCount;
- Started?.Invoke(from, to, forward);
- if (_tcs is not null)
- throw new InvalidOperationException("Transition already running");
- _tcs = new TaskCompletionSource();
- cancellationToken.Register(() => _tcs?.TrySetResult());
- await _tcs.Task;
- _tcs = null;
- if (!cancellationToken.IsCancellationRequested)
- ++FinishCount;
- else
- ++CancelCount;
- }
- public void Complete() => _tcs!.TrySetResult();
- }
- private sealed class TransitionCompletedEventArgsComparer : IEqualityComparer<TransitionCompletedEventArgs>
- {
- public static TransitionCompletedEventArgsComparer Instance { get; } = new();
- public bool Equals(TransitionCompletedEventArgs? x, TransitionCompletedEventArgs? y)
- {
- if (ReferenceEquals(x, y))
- return true;
- if (x is null || y is null)
- return false;
- return x.From == y.From && x.To == y.To && x.HasRunToCompletion == y.HasRunToCompletion;
- }
- public int GetHashCode(TransitionCompletedEventArgs obj)
- => HashCode.Combine(obj.From, obj.To, obj.HasRunToCompletion);
- }
- }
- }
|