using System; using System.Collections.ObjectModel; using System.Linq; using System.Reactive.Subjects; using Avalonia.Animation; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.UnitTests; using Avalonia.VisualTree; using Xunit; namespace Avalonia.Controls.UnitTests { public class CarouselTests : ScopedTestBase { [Fact] public void First_Item_Should_Be_Selected_By_Default() { using var app = Start(); var target = new Carousel { Template = CarouselTemplate(), ItemsSource = new[] { "Foo", "Bar" } }; Prepare(target); Assert.Equal(0, target.SelectedIndex); Assert.Equal("Foo", target.SelectedItem); } [Fact] public void LogicalChild_Should_Be_Selected_Item() { using var app = Start(); var target = new Carousel { Template = CarouselTemplate(), ItemsSource = new[] { "Foo", "Bar" } }; Prepare(target); Assert.Single(target.GetRealizedContainers()); var child = GetContainerTextBlock(target.GetRealizedContainers().Single()); Assert.Equal("Foo", child.Text); } [Fact] public void ViewportFraction_Defaults_To_One() { using var app = Start(); var target = new Carousel(); Assert.Equal(1d, target.ViewportFraction); } [Fact] public void ViewportFraction_Coerces_Invalid_Values_To_One() { using var app = Start(); var target = new Carousel(); target.ViewportFraction = 0; Assert.Equal(1d, target.ViewportFraction); target.ViewportFraction = double.NaN; Assert.Equal(1d, target.ViewportFraction); } [Fact] public void Selected_Item_Changes_To_First_Item_When_Items_Property_Changes() { using var app = Start(); var items = new ObservableCollection { "Foo", "Bar", "FooBar" }; var target = new Carousel { Template = CarouselTemplate(), ItemsSource = items, }; Prepare(target); Assert.Single(target.GetRealizedContainers()); var child = GetContainerTextBlock(target.GetRealizedContainers().Single()); Assert.Equal("Foo", child.Text); var newItems = items.ToList(); newItems.RemoveAt(0); Layout(target); target.ItemsSource = newItems; Layout(target); child = GetContainerTextBlock(target.GetRealizedContainers().Single()); Assert.Equal("Bar", child.Text); } [Fact] public void Selected_Item_Changes_To_First_Item_When_Item_Added() { using var app = Start(); var items = new ObservableCollection(); var target = new Carousel { Template = CarouselTemplate(), ItemsSource = items, }; Prepare(target); Assert.Equal(-1, target.SelectedIndex); Assert.Empty(target.GetRealizedContainers()); items.Add("Foo"); Layout(target); Assert.Equal(0, target.SelectedIndex); Assert.Single(target.GetRealizedContainers()); } [Fact] public void Selected_Index_Changes_To_None_When_Items_Assigned_Null() { using var app = Start(); var items = new ObservableCollection { "Foo", "Bar", "FooBar" }; var target = new Carousel { Template = CarouselTemplate(), ItemsSource = items, }; Prepare(target); Assert.Equal(1, target.GetRealizedContainers().Count()); var child = GetContainerTextBlock(target.GetRealizedContainers().First()); Assert.Equal("Foo", child.Text); target.ItemsSource = null; Layout(target); Assert.Empty(target.GetRealizedContainers()); Assert.Equal(-1, target.SelectedIndex); } [Fact] public void Selected_Index_Is_Maintained_Carousel_Created_With_Non_Zero_SelectedIndex() { using var app = Start(); var items = new ObservableCollection { "Foo", "Bar", "FooBar" }; var target = new Carousel { Template = CarouselTemplate(), ItemsSource = items, SelectedIndex = 2 }; Prepare(target); Assert.Equal("FooBar", target.SelectedItem); var child = GetContainerTextBlock(target.GetRealizedContainers().LastOrDefault()); Assert.Equal("FooBar", child.Text); } [Fact] public void Selected_Item_Changes_To_Next_First_Item_When_Item_Removed_From_Beggining_Of_List() { using var app = Start(); var items = new ObservableCollection { "Foo", "Bar", "FooBar" }; var target = new Carousel { Template = CarouselTemplate(), ItemsSource = items, }; Prepare(target); var child = GetContainerTextBlock(target.GetRealizedContainers().First()); Assert.Equal("Foo", child.Text); items.RemoveAt(0); Layout(target); child = GetContainerTextBlock(target.GetRealizedContainers().First()); Assert.IsType(child); Assert.Equal("Bar", child.Text); } [Fact] public void Selected_Item_Changes_To_First_Item_If_SelectedItem_Is_Removed_From_Middle() { using var app = Start(); var items = new ObservableCollection { "Foo", "Bar", "FooBar" }; var target = new Carousel { Template = CarouselTemplate(), ItemsSource = items, }; Prepare(target); target.SelectedIndex = 1; items.RemoveAt(1); Assert.Equal(0, target.SelectedIndex); Assert.Equal("Foo", target.SelectedItem); } [Fact] public void SelectedItem_Validation() { using (UnitTestApplication.Start(TestServices.MockThreadingInterface)) { var target = new Carousel { Template = CarouselTemplate(), }; Prepare(target); var exception = new System.InvalidCastException("failed validation"); var textObservable = new BehaviorSubject(new BindingNotification(exception, BindingErrorType.DataValidationError)); target.Bind(ComboBox.SelectedItemProperty, textObservable); Assert.True(DataValidationErrors.GetHasErrors(target)); Assert.Equal([exception], DataValidationErrors.GetErrors(target)); } } [Fact] public void Can_Move_Forward_Back_Forward() { using var app = Start(); var items = new[] { "foo", "bar" }; var target = new Carousel { Template = CarouselTemplate(), ItemsSource = items, }; Prepare(target); target.SelectedIndex = 1; Layout(target); Assert.Equal(1, target.SelectedIndex); target.SelectedIndex = 0; Layout(target); Assert.Equal(0, target.SelectedIndex); target.SelectedIndex = 1; Layout(target); Assert.Equal(1, target.SelectedIndex); } [Fact] public void Can_Move_Forward_Back_Forward_With_Control_Items() { // Issue #11119 using var app = Start(); var items = new[] { new Canvas(), new Canvas() }; var target = new Carousel { Template = CarouselTemplate(), ItemsSource = items, }; Prepare(target); target.SelectedIndex = 1; Layout(target); Assert.Equal(1, target.SelectedIndex); target.SelectedIndex = 0; Layout(target); Assert.Equal(0, target.SelectedIndex); target.SelectedIndex = 1; target.PropertyChanged += (s, e) => { if (e.Property == Carousel.SelectedIndexProperty) { } }; Layout(target); Assert.Equal(1, target.SelectedIndex); } public class WrapSelectionTests : ScopedTestBase { [Fact] public void Next_Loops_When_WrapSelection_Is_True() { using var app = Start(); var items = new[] { "foo", "bar", "baz" }; var target = new Carousel { Template = CarouselTemplate(), ItemsSource = items, WrapSelection = true, SelectedIndex = 2 }; Prepare(target); target.Next(); Layout(target); Assert.Equal(0, target.SelectedIndex); } [Fact] public void Previous_Loops_When_WrapSelection_Is_True() { using var app = Start(); var items = new[] { "foo", "bar", "baz" }; var target = new Carousel { Template = CarouselTemplate(), ItemsSource = items, WrapSelection = true, SelectedIndex = 0 }; Prepare(target); target.Previous(); Layout(target); Assert.Equal(2, target.SelectedIndex); } [Fact] public void Next_Does_Not_Loop_When_WrapSelection_Is_False() { using var app = Start(); var items = new[] { "foo", "bar", "baz" }; var target = new Carousel { Template = CarouselTemplate(), ItemsSource = items, WrapSelection = false, SelectedIndex = 2 }; Prepare(target); target.Next(); Layout(target); Assert.Equal(2, target.SelectedIndex); } [Fact] public void Previous_Does_Not_Loop_When_WrapSelection_Is_False() { using var app = Start(); var items = new[] { "foo", "bar", "baz" }; var target = new Carousel { Template = CarouselTemplate(), ItemsSource = items, WrapSelection = false, SelectedIndex = 0 }; Prepare(target); target.Previous(); Layout(target); Assert.Equal(0, target.SelectedIndex); } } [Fact] public void Right_Arrow_Navigates_To_Next_With_Horizontal_PageSlide() { using var app = Start(); var target = new Carousel { Template = CarouselTemplate(), ItemsSource = new[] { "Foo", "Bar", "Baz" }, PageTransition = new PageSlide(TimeSpan.FromMilliseconds(100), PageSlide.SlideAxis.Horizontal), }; Prepare(target); Assert.Equal(0, target.SelectedIndex); target.RaiseEvent(new KeyEventArgs { RoutedEvent = InputElement.KeyDownEvent, Key = Key.Right }); Assert.Equal(1, target.SelectedIndex); } [Fact] public void Down_Arrow_Navigates_To_Next_With_Vertical_PageSlide() { using var app = Start(); var target = new Carousel { Template = CarouselTemplate(), ItemsSource = new[] { "Foo", "Bar", "Baz" }, PageTransition = new PageSlide(TimeSpan.FromMilliseconds(100), PageSlide.SlideAxis.Vertical), }; Prepare(target); Assert.Equal(0, target.SelectedIndex); target.RaiseEvent(new KeyEventArgs { RoutedEvent = InputElement.KeyDownEvent, Key = Key.Down }); Assert.Equal(1, target.SelectedIndex); } [Fact] public void Home_Navigates_To_First_Item() { using var app = Start(); var target = new Carousel { Template = CarouselTemplate(), ItemsSource = new[] { "Foo", "Bar", "Baz" }, SelectedIndex = 2, }; Prepare(target); Layout(target); Assert.Equal(2, target.SelectedIndex); target.RaiseEvent(new KeyEventArgs { RoutedEvent = InputElement.KeyDownEvent, Key = Key.Home }); Assert.Equal(0, target.SelectedIndex); } [Fact] public void End_Navigates_To_Last_Item() { using var app = Start(); var target = new Carousel { Template = CarouselTemplate(), ItemsSource = new[] { "Foo", "Bar", "Baz" }, }; Prepare(target); Assert.Equal(0, target.SelectedIndex); target.RaiseEvent(new KeyEventArgs { RoutedEvent = InputElement.KeyDownEvent, Key = Key.End }); Assert.Equal(2, target.SelectedIndex); } [Fact] public void Wrong_Axis_Arrow_Is_Ignored() { using var app = Start(); var target = new Carousel { Template = CarouselTemplate(), ItemsSource = new[] { "Foo", "Bar", "Baz" }, PageTransition = new PageSlide(TimeSpan.FromMilliseconds(100), PageSlide.SlideAxis.Horizontal), }; Prepare(target); Assert.Equal(0, target.SelectedIndex); target.RaiseEvent(new KeyEventArgs { RoutedEvent = InputElement.KeyDownEvent, Key = Key.Down }); Assert.Equal(0, target.SelectedIndex); } [Fact] public void Left_Arrow_Wraps_With_WrapSelection() { using var app = Start(); var target = new Carousel { Template = CarouselTemplate(), ItemsSource = new[] { "Foo", "Bar", "Baz" }, PageTransition = new PageSlide(TimeSpan.FromMilliseconds(100), PageSlide.SlideAxis.Horizontal), WrapSelection = true, }; Prepare(target); Assert.Equal(0, target.SelectedIndex); target.RaiseEvent(new KeyEventArgs { RoutedEvent = InputElement.KeyDownEvent, Key = Key.Left }); Assert.Equal(2, target.SelectedIndex); } private static IDisposable Start() => UnitTestApplication.Start(TestServices.MockPlatformRenderInterface); private static void Prepare(Carousel target) { var root = new TestRoot(target); root.LayoutManager.ExecuteInitialLayoutPass(); } private static void Layout(Carousel target) { target.GetLayoutManager()?.ExecuteLayoutPass(); } 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((parent, scope) => new Panel { Children = { new ScrollContentPresenter { Name = "PART_ContentPresenter", }.RegisterInNameScope(scope), } }); } private static TextBlock GetContainerTextBlock(object? control) { var contentPresenter = Assert.IsType(control); return Assert.IsType(contentPresenter.Child); } } }