csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
265 lines
9.3 KiB
265 lines
9.3 KiB
using System;
|
|
using System.Collections;
|
|
using System.Collections.ObjectModel;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Avalonia.Animation;
|
|
using Avalonia.Controls.Presenters;
|
|
using Avalonia.Controls.Primitives;
|
|
using Avalonia.Controls.Templates;
|
|
using Avalonia.Layout;
|
|
using Avalonia.UnitTests;
|
|
using Avalonia.VisualTree;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
#nullable enable
|
|
|
|
namespace Avalonia.Controls.UnitTests
|
|
{
|
|
public class VirtualizingCarouselPanelTests
|
|
{
|
|
[Fact]
|
|
public void Initial_Item_Is_Displayed()
|
|
{
|
|
using var app = Start();
|
|
var items = new[] { "foo", "bar" };
|
|
var (target, _) = CreateTarget(items);
|
|
|
|
Assert.Single(target.Children);
|
|
var container = Assert.IsType<ContentPresenter>(target.Children[0]);
|
|
Assert.Equal("foo", container.Content);
|
|
}
|
|
|
|
[Fact]
|
|
public void Displays_Next_Item()
|
|
{
|
|
using var app = Start();
|
|
var items = new[] { "foo", "bar" };
|
|
var (target, carousel) = CreateTarget(items);
|
|
|
|
carousel.SelectedIndex = 1;
|
|
Layout(target);
|
|
|
|
Assert.Single(target.Children);
|
|
var container = Assert.IsType<ContentPresenter>(target.Children[0]);
|
|
Assert.Equal("bar", container.Content);
|
|
}
|
|
|
|
[Fact]
|
|
public void Handles_Inserted_Item()
|
|
{
|
|
using var app = Start();
|
|
var items = new ObservableCollection<string> { "foo", "bar" };
|
|
var (target, carousel) = CreateTarget(items);
|
|
var container = Assert.IsType<ContentPresenter>(target.Children[0]);
|
|
|
|
items.Insert(0, "baz");
|
|
Layout(target);
|
|
|
|
Assert.Single(target.Children);
|
|
Assert.Same(container, target.Children[0]);
|
|
Assert.Equal("foo", container.Content);
|
|
}
|
|
|
|
[Fact]
|
|
public void Handles_Removed_Item()
|
|
{
|
|
using var app = Start();
|
|
var items = new ObservableCollection<string> { "foo", "bar" };
|
|
var (target, carousel) = CreateTarget(items);
|
|
var container = Assert.IsType<ContentPresenter>(target.Children[0]);
|
|
|
|
items.RemoveAt(0);
|
|
Layout(target);
|
|
|
|
Assert.Single(target.Children);
|
|
Assert.Same(container, target.Children[0]);
|
|
Assert.Equal("bar", container.Content);
|
|
}
|
|
|
|
[Fact]
|
|
public void Handles_Replaced_Item()
|
|
{
|
|
using var app = Start();
|
|
var items = new ObservableCollection<string> { "foo", "bar" };
|
|
var (target, carousel) = CreateTarget(items);
|
|
var container = Assert.IsType<ContentPresenter>(target.Children[0]);
|
|
|
|
items[0] = "baz";
|
|
Layout(target);
|
|
|
|
Assert.Single(target.Children);
|
|
Assert.Same(container, target.Children[0]);
|
|
Assert.Equal("baz", container.Content);
|
|
}
|
|
|
|
[Fact]
|
|
public void Handles_Moved_Item()
|
|
{
|
|
using var app = Start();
|
|
var items = new ObservableCollection<string> { "foo", "bar" };
|
|
var (target, carousel) = CreateTarget(items);
|
|
var container = Assert.IsType<ContentPresenter>(target.Children[0]);
|
|
|
|
items.Move(0, 1);
|
|
Layout(target);
|
|
|
|
Assert.Single(target.Children);
|
|
Assert.Same(container, target.Children[0]);
|
|
Assert.Equal("bar", container.Content);
|
|
}
|
|
|
|
public class Transitions
|
|
{
|
|
[Fact]
|
|
public void Initial_Item_Does_Not_Start_Transition()
|
|
{
|
|
using var app = Start();
|
|
var items = new Control[] { new Button(), new Canvas() };
|
|
var transition = new Mock<IPageTransition>();
|
|
var (target, _) = CreateTarget(items, transition.Object);
|
|
|
|
transition.Verify(x => x.Start(
|
|
It.IsAny<Visual>(),
|
|
It.IsAny<Visual>(),
|
|
It.IsAny<bool>(),
|
|
It.IsAny<CancellationToken>()),
|
|
Times.Never);
|
|
}
|
|
|
|
[Fact]
|
|
public void Changing_SelectedIndex_Starts_Transition()
|
|
{
|
|
using var app = Start();
|
|
var items = new Control[] { new Button(), new Canvas() };
|
|
var transition = new Mock<IPageTransition>();
|
|
var (target, carousel) = CreateTarget(items, transition.Object);
|
|
|
|
carousel.SelectedIndex = 1;
|
|
Layout(target);
|
|
|
|
transition.Verify(x => x.Start(
|
|
items[0],
|
|
items[1],
|
|
true,
|
|
It.IsAny<CancellationToken>()),
|
|
Times.Once);
|
|
}
|
|
|
|
[Fact]
|
|
public void TransitionFrom_Control_Is_Recycled_When_Transition_Completes()
|
|
{
|
|
using var app = Start();
|
|
using var sync = UnitTestSynchronizationContext.Begin();
|
|
var items = new Control[] { new Button(), new Canvas() };
|
|
var transition = new Mock<IPageTransition>();
|
|
var (target, carousel) = CreateTarget(items, transition.Object);
|
|
var transitionTask = new TaskCompletionSource();
|
|
|
|
transition.Setup(x => x.Start(
|
|
items[0],
|
|
items[1],
|
|
true,
|
|
It.IsAny<CancellationToken>()))
|
|
.Returns(() => transitionTask.Task);
|
|
|
|
carousel.SelectedIndex = 1;
|
|
Layout(target);
|
|
|
|
Assert.Equal(items, target.Children);
|
|
Assert.All(items, x => Assert.True(x.IsVisible));
|
|
|
|
transitionTask.SetResult();
|
|
sync.ExecutePostedCallbacks();
|
|
|
|
Assert.Equal(items, target.Children);
|
|
Assert.False(items[0].IsVisible);
|
|
Assert.True(items[1].IsVisible);
|
|
}
|
|
|
|
[Fact]
|
|
public void Existing_Transition_Is_Canceled_If_Interrupted()
|
|
{
|
|
using var app = Start();
|
|
using var sync = UnitTestSynchronizationContext.Begin();
|
|
var items = new Control[] { new Button(), new Canvas() };
|
|
var transition = new Mock<IPageTransition>();
|
|
var (target, carousel) = CreateTarget(items, transition.Object);
|
|
var transitionTask = new TaskCompletionSource();
|
|
CancellationToken? cancelationToken = null;
|
|
|
|
transition.Setup(x => x.Start(
|
|
items[0],
|
|
items[1],
|
|
true,
|
|
It.IsAny<CancellationToken>()))
|
|
.Callback<Visual, Visual, bool, CancellationToken>((_, _, _, c) => cancelationToken = c)
|
|
.Returns(() => transitionTask.Task);
|
|
|
|
carousel.SelectedIndex = 1;
|
|
Layout(target);
|
|
|
|
Assert.NotNull(cancelationToken);
|
|
Assert.False(cancelationToken!.Value.IsCancellationRequested);
|
|
|
|
carousel.SelectedIndex = 0;
|
|
Layout(target);
|
|
|
|
Assert.True(cancelationToken!.Value.IsCancellationRequested);
|
|
}
|
|
}
|
|
|
|
private static IDisposable Start() => UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
|
|
|
|
private static (VirtualizingCarouselPanel, Carousel) CreateTarget(
|
|
IEnumerable items,
|
|
IPageTransition? transition = null)
|
|
{
|
|
var carousel = new Carousel
|
|
{
|
|
ItemsSource = items,
|
|
Template = CarouselTemplate(),
|
|
PageTransition = transition,
|
|
};
|
|
|
|
var root = new TestRoot(carousel);
|
|
root.LayoutManager.ExecuteInitialLayoutPass();
|
|
return ((VirtualizingCarouselPanel)carousel.Presenter!.Panel!, carousel);
|
|
}
|
|
|
|
private static IControlTemplate CarouselTemplate()
|
|
{
|
|
return new FuncControlTemplate((c, ns) =>
|
|
new ScrollViewer
|
|
{
|
|
Name = "PART_ScrollViewer",
|
|
Template = ScrollViewerTemplate(),
|
|
HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden,
|
|
VerticalScrollBarVisibility = ScrollBarVisibility.Hidden,
|
|
Content = new ItemsPresenter
|
|
{
|
|
Name = "PART_ItemsPresenter",
|
|
[~ItemsPresenter.ItemsPanelProperty] = c[~ItemsControl.ItemsPanelProperty],
|
|
}.RegisterInNameScope(ns)
|
|
}.RegisterInNameScope(ns));
|
|
}
|
|
|
|
private static FuncControlTemplate ScrollViewerTemplate()
|
|
{
|
|
return new FuncControlTemplate<ScrollViewer>((parent, scope) =>
|
|
new Panel
|
|
{
|
|
Children =
|
|
{
|
|
new ScrollContentPresenter
|
|
{
|
|
Name = "PART_ContentPresenter",
|
|
}.RegisterInNameScope(scope),
|
|
}
|
|
});
|
|
}
|
|
|
|
private static void Layout(Control c) => ((ILayoutRoot)c.GetVisualRoot()!).LayoutManager.ExecuteLayoutPass();
|
|
}
|
|
}
|
|
|