committed by
GitHub
6 changed files with 321 additions and 122 deletions
@ -1,66 +1,243 @@ |
|||||
using System; |
using System; |
||||
using Avalonia.LogicalTree; |
using System.Linq; |
||||
using Avalonia.UnitTests; |
|
||||
using Xunit; |
|
||||
using System.Threading; |
using System.Threading; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
using Avalonia.Animation; |
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 |
namespace Avalonia.Controls.UnitTests |
||||
{ |
{ |
||||
public class TransitioningContentControlTests |
public class TransitioningContentControlTests |
||||
{ |
{ |
||||
[Fact] |
[Fact] |
||||
public void Old_Content_Shuold_Be_Removed__From_Logical_Tree_After_Out_Animation() |
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 TransitionContentPresenter_Should_Initially_Be_Hidden() |
||||
|
{ |
||||
|
using var app = Start(); |
||||
|
var (target, transition) = CreateTarget("foo"); |
||||
|
var transitionPresenter = GetTransitionContentPresenter(target); |
||||
|
|
||||
|
Assert.False(transitionPresenter.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 ContentPresenters_Should_Be_Setup_For_Transition() |
||||
|
{ |
||||
|
using var app = Start(); |
||||
|
var (target, transition) = CreateTarget("foo"); |
||||
|
var transitionPresenter = GetTransitionContentPresenter(target); |
||||
|
|
||||
|
target.Content = "bar"; |
||||
|
Layout(target); |
||||
|
|
||||
|
Assert.True(transitionPresenter.IsVisible); |
||||
|
Assert.Equal("bar", target.Presenter!.Content); |
||||
|
Assert.Equal("foo", transitionPresenter.Content); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void TransitionContentPresenter_Should_Be_Hidden_When_Transition_Completes() |
||||
|
{ |
||||
|
using var app = Start(); |
||||
|
using var sync = UnitTestSynchronizationContext.Begin(); |
||||
|
var (target, transition) = CreateTarget("foo"); |
||||
|
var transitionPresenter = GetTransitionContentPresenter(target); |
||||
|
|
||||
|
target.Content = "bar"; |
||||
|
Layout(target); |
||||
|
Assert.True(transitionPresenter.IsVisible); |
||||
|
|
||||
|
transition.Complete(); |
||||
|
sync.ExecutePostedCallbacks(); |
||||
|
|
||||
|
Assert.False(transitionPresenter.IsVisible); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Transition_Should_Be_Canceled_If_Content_Changes_While_Running() |
||||
{ |
{ |
||||
using (UnitTestApplication.Start(TestServices.StyledWindow)) |
using var app = Start(); |
||||
|
using var sync = UnitTestSynchronizationContext.Begin(); |
||||
|
var (target, transition) = CreateTarget("foo"); |
||||
|
var transitionPresenter = GetTransitionContentPresenter(target); |
||||
|
|
||||
|
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 transitionPresenter = GetTransitionContentPresenter(target); |
||||
|
|
||||
|
target.Content = "bar"; |
||||
|
Layout(target); |
||||
|
|
||||
|
target.Content = "baz"; |
||||
|
|
||||
|
var startedRaised = 0; |
||||
|
|
||||
|
transition.Started += (from, to, forward) => |
||||
{ |
{ |
||||
var testTransition = new TestTransition(); |
var fromPresenter = Assert.IsType<ContentPresenter>(from); |
||||
|
var toPresenter = Assert.IsType<ContentPresenter>(to); |
||||
|
|
||||
|
Assert.Same(transitionPresenter, fromPresenter); |
||||
|
Assert.Same(target.Presenter, toPresenter); |
||||
|
Assert.Equal("bar", fromPresenter.Content); |
||||
|
Assert.Equal("baz", toPresenter.Content); |
||||
|
Assert.True(forward); |
||||
|
Assert.Equal(1, transition.CancelCount); |
||||
|
|
||||
var target = new TransitioningContentControl(); |
++startedRaised; |
||||
target.PageTransition = testTransition; |
}; |
||||
|
|
||||
var root = new TestRoot() { Child = target }; |
Layout(target); |
||||
|
sync.ExecutePostedCallbacks(); |
||||
|
|
||||
|
Assert.Equal(1, startedRaised); |
||||
|
Assert.Equal("baz", target.Presenter!.Content); |
||||
|
Assert.Equal("bar", transitionPresenter.Content); |
||||
|
} |
||||
|
|
||||
var oldControl = new Control(); |
private static IDisposable Start() |
||||
var newControl = new Control(); |
{ |
||||
|
return UnitTestApplication.Start( |
||||
|
TestServices.MockThreadingInterface.With( |
||||
|
fontManagerImpl: new HeadlessFontManagerStub(), |
||||
|
renderInterface: new HeadlessPlatformRenderInterface(), |
||||
|
textShaperImpl: new HeadlessTextShaperStub())); |
||||
|
} |
||||
|
|
||||
target.Content = oldControl; |
private static (TransitioningContentControl, TestTransition) CreateTarget(object content) |
||||
Threading.Dispatcher.UIThread.RunJobs(); |
{ |
||||
|
var transition = new TestTransition(); |
||||
|
var target = new TransitioningContentControl |
||||
|
{ |
||||
|
Content = content, |
||||
|
PageTransition = transition, |
||||
|
Template = CreateTemplate(), |
||||
|
}; |
||||
|
|
||||
Assert.Equal(target, oldControl.GetLogicalParent()); |
var root = new TestRoot(target); |
||||
Assert.Equal(null, newControl.GetLogicalParent()); |
root.LayoutManager.ExecuteInitialLayoutPass(); |
||||
|
return (target, transition); |
||||
|
} |
||||
|
|
||||
testTransition.BeginTransition += isFrom => |
private static IControlTemplate CreateTemplate() |
||||
|
{ |
||||
|
return new FuncControlTemplate((x, ns) => |
||||
|
{ |
||||
|
return new Panel |
||||
{ |
{ |
||||
// Old out
|
Children = |
||||
if (isFrom) |
|
||||
{ |
{ |
||||
Assert.Equal(target, oldControl.GetLogicalParent()); |
new ContentPresenter |
||||
Assert.Equal(null, newControl.GetLogicalParent()); |
{ |
||||
} |
Name = "PART_ContentPresenter", |
||||
// New in
|
[!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty], |
||||
else |
}, |
||||
{ |
new ContentPresenter |
||||
Assert.Equal(null, oldControl.GetLogicalParent()); |
{ |
||||
Assert.Equal(target, newControl.GetLogicalParent()); |
Name = "PART_TransitionContentPresenter", |
||||
|
}, |
||||
} |
} |
||||
}; |
}; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
target.Content = newControl; |
private static ContentPresenter GetTransitionContentPresenter(TransitioningContentControl target) |
||||
Threading.Dispatcher.UIThread.RunJobs(); |
{ |
||||
} |
return Assert.IsType<ContentPresenter>(target |
||||
|
.GetTemplateChildren() |
||||
|
.First(x => x.Name == "PART_TransitionContentPresenter")); |
||||
} |
} |
||||
} |
|
||||
public class TestTransition : IPageTransition |
|
||||
{ |
|
||||
public event Action<bool> BeginTransition; |
|
||||
|
|
||||
public Task Start(Visual from, Visual to, bool forward, CancellationToken cancellationToken) |
private void Layout(Control c) |
||||
{ |
{ |
||||
bool isFrom = from != null && to == null; |
(c.GetVisualRoot() as ILayoutRoot)?.LayoutManager.ExecuteLayoutPass(); |
||||
BeginTransition?.Invoke(isFrom); |
} |
||||
return Task.CompletedTask; |
|
||||
|
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(); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
Loading…
Reference in new issue