using System; using System.Collections.ObjectModel; using System.Linq; using System.Reactive.Subjects; using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.UnitTests; using Avalonia.VisualTree; using Xunit; namespace Avalonia.Controls.UnitTests { public class CarouselTests { [Fact] public void First_Item_Should_Be_Selected_By_Default() { using var app = Start(); var target = new Carousel { Template = CarouselTemplate(), Items = 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(), Items = new[] { "Foo", "Bar" } }; Prepare(target); Assert.Single(target.GetRealizedContainers()); var child = GetContainerTextBlock(target.GetRealizedContainers().Single()); Assert.Equal("Foo", child.Text); } [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(), Items = 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.Items = 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(), Items = 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(), Items = items, }; Prepare(target); Assert.Equal(1, target.GetRealizedContainers().Count()); var child = GetContainerTextBlock(target.GetRealizedContainers().First()); Assert.Equal("Foo", child.Text); target.Items = null; Layout(target); var numChildren = target.GetRealizedContainers().Count(); 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(), Items = 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(), Items = 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(), Items = 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.True(DataValidationErrors.GetErrors(target).SequenceEqual(new[] { exception })); } } 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) { ((ILayoutRoot)target.GetVisualRoot()).LayoutManager.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", [~ScrollContentPresenter.ContentProperty] = parent.GetObservable(ScrollViewer.ContentProperty).ToBinding(), [~~ScrollContentPresenter.ExtentProperty] = parent[~~ScrollViewer.ExtentProperty], [~~ScrollContentPresenter.OffsetProperty] = parent[~~ScrollViewer.OffsetProperty], [~~ScrollContentPresenter.ViewportProperty] = parent[~~ScrollViewer.ViewportProperty], [~ScrollContentPresenter.CanHorizontallyScrollProperty] = parent[~ScrollViewer.CanHorizontallyScrollProperty], [~ScrollContentPresenter.CanVerticallyScrollProperty] = parent[~ScrollViewer.CanVerticallyScrollProperty], }.RegisterInNameScope(scope), } }); } private static TextBlock GetContainerTextBlock(object control) { var contentPresenter = Assert.IsType(control); return Assert.IsType(contentPresenter.Child); } } }