From 670eea0977f005263e7586227532da2085fc00b0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 4 Apr 2023 12:49:38 +0200 Subject: [PATCH] Modernized ItemsControl tests. --- .../ItemsControlTests.cs | 772 ++++++++---------- 1 file changed, 336 insertions(+), 436 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index 41aaa7b670..98662055ea 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; @@ -8,11 +9,15 @@ using Avalonia.Controls.Presenters; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; +using Avalonia.Layout; using Avalonia.LogicalTree; using Avalonia.Styling; using Avalonia.UnitTests; +using Avalonia.VisualTree; using Xunit; +#nullable enable + namespace Avalonia.Controls.UnitTests { public class ItemsControlTests @@ -20,12 +25,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Setting_ItemsSource_Should_Populate_Items() { - var target = new ItemsControl - { - Template = GetTemplate(), - ItemTemplate = new FuncDataTemplate((_, __) => new Canvas()), - ItemsSource = new[] { "foo", "bar" }, - }; + using var app = Start(); + var target = CreateTarget(itemsSource: new[] { "foo", "bar" }); Assert.NotSame(target.ItemsSource, target.Items); Assert.Equal(target.ItemsSource, target.Items); @@ -34,12 +35,9 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Cannot_Set_ItemsSource_With_Items_Present() { - var target = new ItemsControl - { - Template = GetTemplate(), - ItemTemplate = new FuncDataTemplate((_, __) => new Canvas()), - Items = { "foo", "bar" }, - }; + using var app = Start(); + var target = CreateTarget(); + target.Items.Add("foo"); Assert.Throws(() => target.ItemsSource = new[] { "baz" }); } @@ -47,12 +45,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Cannot_Modify_Items_When_ItemsSource_Set() { - var target = new ItemsControl - { - Template = GetTemplate(), - ItemTemplate = new FuncDataTemplate((_, __) => new Canvas()), - ItemsSource = Array.Empty(), - }; + using var app = Start(); + var target = CreateTarget(itemsSource: Array.Empty()); Assert.Throws(() => target.Items.Add("foo")); } @@ -60,18 +54,11 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Should_Use_ItemTemplate_To_Create_Control() { - var target = new ItemsControl - { - Template = GetTemplate(), - ItemTemplate = new FuncDataTemplate((_, __) => new Canvas()), - }; - - target.ItemsSource = new[] { "Foo" }; - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - - var container = (ContentPresenter)target.Presenter.Panel.Children[0]; - container.UpdateChild(); + using var app = Start(); + var target = CreateTarget( + itemsSource: new[] { "Foo" }, + itemTemplate: new FuncDataTemplate((_, __) => new Canvas())); + var container = GetContainer(target); Assert.IsType(container.Child); } @@ -79,24 +66,18 @@ namespace Avalonia.Controls.UnitTests [Fact] public void ItemTemplate_Can_Be_Changed() { - var target = new ItemsControl - { - Template = GetTemplate(), - ItemTemplate = new FuncDataTemplate((_, __) => new Canvas()), - }; - - target.ItemsSource = new[] { "Foo" }; - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - - var container = (ContentPresenter)target.Presenter.Panel.Children[0]; - container.UpdateChild(); + using var app = Start(); + var target = CreateTarget( + itemsSource: new[] { "Foo" }, + itemTemplate: new FuncDataTemplate((_, __) => new Canvas())); + var container = GetContainer(target); Assert.IsType(container.Child); target.ItemTemplate = new FuncDataTemplate((_, __) => new Border()); - container = (ContentPresenter)target.Presenter.Panel.Children[0]; - container.UpdateChild(); + Layout(target); + + container = GetContainer(target); Assert.IsType(container.Child); } @@ -104,40 +85,28 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Panel_Should_Have_TemplatedParent_Set_To_ItemsControl() { - var target = new ItemsControl(); - - target.Template = GetTemplate(); - target.ItemsSource = new[] { "Foo" }; - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); + using var app = Start(); + var target = CreateTarget(itemsSource: new[] { "Foo" }); - Assert.Equal(target, target.Presenter.Panel.TemplatedParent); + Assert.Equal(target, target.ItemsPanelRoot?.TemplatedParent); } [Fact] public void Panel_Should_Have_ItemsHost_Set_To_True() { - var target = new ItemsControl(); + using var app = Start(); + var target = CreateTarget(itemsSource: new[] { "Foo" }); - target.Template = GetTemplate(); - target.ItemsSource = new[] { "Foo" }; - target.ApplyTemplate(); - target.Presenter!.ApplyTemplate(); - - Assert.True(target.Presenter.Panel!.IsItemsHost); + Assert.True(target.ItemsPanelRoot?.IsItemsHost); } [Fact] public void Container_Should_Have_TemplatedParent_Set_To_Null() { - var target = new ItemsControl(); + using var app = Start(); + var target = CreateTarget(itemsSource: new[] { "Foo" }); - target.Template = GetTemplate(); - target.ItemsSource = new[] { "Foo" }; - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - - var container = (ContentPresenter)target.Presenter.Panel.Children[0]; + var container = GetContainer(target); Assert.Null(container.TemplatedParent); } @@ -145,18 +114,13 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Container_Should_Have_Theme_Set_To_ItemContainerTheme() { + using var app = Start(); var theme = new ControlTheme { TargetType = typeof(ContentPresenter) }; - var target = new ItemsControl - { - ItemContainerTheme = theme, - }; - - target.Template = GetTemplate(); - target.ItemsSource = new[] { "Foo" }; - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); + var target = CreateTarget( + itemsSource: new[] { "Foo" }, + itemContainerTheme: theme); - var container = (ContentPresenter)target.Presenter.Panel.Children[0]; + var container = GetContainer(target); Assert.Same(container.Theme, theme); } @@ -164,38 +128,31 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Container_Should_Have_LogicalParent_Set_To_ItemsControl() { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var root = new Window(); - var target = new ItemsControl(); - - root.Content = target; - - var templatedParent = new Button(); - target.TemplatedParent = templatedParent; - target.Template = GetTemplate(); + using var app = UnitTestApplication.Start(TestServices.StyledWindow); + var target = new ItemsControl(); + var root = CreateRoot(target); + var templatedParent = new Button(); - target.ItemsSource = new[] { "Foo" }; + target.TemplatedParent = templatedParent; + target.Template = CreateItemsControlTemplate(); + target.ItemsSource = new[] { "Foo" }; - root.ApplyTemplate(); - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); + root.LayoutManager.ExecuteInitialLayoutPass(); - var container = (ContentPresenter)target.Presenter.Panel.Children[0]; + var container = GetContainer(target); - Assert.Equal(target, container.Parent); - } + Assert.Equal(target, container.Parent); } [Fact] public void Control_Item_Should_Be_Logical_Child_Before_ApplyTemplate() { - var target = new ItemsControl(); + using var app = Start(); var child = new Control(); + var target = CreateTarget(items: new[] { child }, performLayout: false); - target.Template = GetTemplate(); - target.Items.Add(child); - + Assert.False(target.IsMeasureValid); + Assert.Empty(target.GetVisualChildren()); Assert.Equal(child.Parent, target); Assert.Equal(child.GetLogicalParent(), target); Assert.Equal(new[] { child }, target.GetLogicalChildren()); @@ -204,17 +161,12 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Control_Item_Should_Be_Logical_Child_After_Layout() { - var target = new ItemsControl - { - Template = GetTemplate(), - }; - var root = new TestRoot(target); + using var app = Start(); var child = new Control(); + var target = CreateTarget(items: new[] { child }); - target.Template = GetTemplate(); - target.Items.Add(child); - root.LayoutManager.ExecuteInitialLayoutPass(); - + Assert.True(target.IsMeasureValid); + Assert.Single(target.GetVisualChildren()); Assert.Equal(target, child.Parent); Assert.Equal(target, child.GetLogicalParent()); Assert.Equal(new[] { child }, target.GetLogicalChildren()); @@ -223,34 +175,25 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Added_Container_Should_Have_LogicalParent_Set_To_ItemsControl() { - var item = new Border(); + using var app = Start(); var items = new ObservableCollection(); + var target = CreateTarget(itemsSource: items); - var target = new ItemsControl - { - Template = GetTemplate(), - ItemsSource = items, - }; - - var root = new TestRoot(true, target); - - root.Measure(new Size(100, 100)); - root.Arrange(new Rect(0, 0, 100, 100)); - + var item = new Border(); items.Add(item); Assert.Equal(target, item.Parent); } [Fact] - public void Control_Item_Should_Be_Removed_From_Logical_Children_Before_ApplyTemplate() + public void Control_Item_Can_Be_Removed_From_Logical_Children_Before_ApplyTemplate() { - var target = new ItemsControl(); + using var app = Start(); var child = new Control(); + var target = CreateTarget(items: new[] { child }, performLayout: false); - target.Template = GetTemplate(); - target.Items.Add(child); - + Assert.False(target.IsMeasureValid); + Assert.Empty(target.GetVisualChildren()); Assert.Single(target.GetLogicalChildren()); target.Items.RemoveAt(0); @@ -263,25 +206,26 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clearing_Items_Should_Clear_Child_Controls_Parent_Before_ApplyTemplate() { - var target = new ItemsControl(); + using var app = Start(); var child = new Control(); + var target = CreateTarget(items: new[] { child }, performLayout: false); - target.Template = GetTemplate(); - target.Items.Add(child); - + Assert.False(target.IsMeasureValid); + Assert.Empty(target.GetVisualChildren()); Assert.Single(target.GetLogicalChildren()); target.Items.Clear(); Assert.Null(child.Parent); - Assert.Null(((ILogical)child).LogicalParent); + Assert.Null(child.GetLogicalParent()); } [Fact] public void Assigning_ItemsSource_Should_Not_Fire_LogicalChildren_CollectionChanged_Before_ApplyTemplate() { - var target = new ItemsControl(); + using var app = Start(); var child = new Control(); + var target = CreateTarget(itemsSource: new[] { child }, performLayout: false); var called = false; ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true; @@ -292,11 +236,27 @@ namespace Avalonia.Controls.UnitTests Assert.False(called); } + [Fact] + public void Removing_ItemsSource_Items_Should_Not_Fire_LogicalChildren_CollectionChanged_Before_ApplyTemplate() + { + using var app = Start(); + var items = new AvaloniaList { "Foo", "Bar" }; + var target = CreateTarget(itemsSource: items, performLayout: false); + var called = false; + + ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true; + + items.Remove("Bar"); + + Assert.False(called); + } + [Fact] public void Changing_ItemsSource_Should_Not_Fire_LogicalChildren_CollectionChanged_Before_ApplyTemplate() { - var target = new ItemsControl(); + using var app = Start(); var child = new Control(); + var target = CreateTarget(itemsSource: new[] { child }, performLayout: false); var called = false; ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true; @@ -311,12 +271,10 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clearing_Items_Should_Clear_Child_Controls_Parent() { - var target = new ItemsControl(); + using var app = Start(); var child = new Control(); + var target = CreateTarget(items: new[] { child }); - target.Template = GetTemplate(); - target.Items.Add(child); - target.ApplyTemplate(); target.Items.Clear(); Assert.Null(child.Parent); @@ -326,16 +284,14 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Adding_Control_Item_Should_Make_Control_Appear_In_LogicalChildren() { - var target = new ItemsControl(); + using var app = Start(); var child = new Control(); - - target.Template = GetTemplate(); - target.Items.Add(child); + var target = CreateTarget(items: new[] { child }, performLayout: false); // Should appear both before and after applying template. Assert.Equal(new ILogical[] { child }, target.GetLogicalChildren()); - target.ApplyTemplate(); + Layout(target); Assert.Equal(new ILogical[] { child }, target.GetLogicalChildren()); } @@ -343,14 +299,10 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Adding_String_Item_Should_Make_ContentPresenter_Appear_In_LogicalChildren() { - var target = new ItemsControl(); - - target.Template = GetTemplate(); - target.ItemsSource = new[] { "Foo" }; - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - + using var app = Start(); + var target = CreateTarget(itemsSource: new[] { "Foo " }); var logical = (ILogical)target; + Assert.Equal(1, logical.LogicalChildren.Count); Assert.IsType(logical.LogicalChildren[0]); } @@ -358,16 +310,17 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Adding_Items_Should_Fire_LogicalChildren_CollectionChanged() { - var target = new ItemsControl(); - var child = new Control(); + using var app = Start(); + var target = CreateTarget(); var called = false; - target.Template = GetTemplate(); + target.Template = CreateItemsControlTemplate(); target.ApplyTemplate(); ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = e.Action == NotifyCollectionChangedAction.Add; + var child = new Control(); target.Items.Add(child); Assert.True(called); @@ -376,14 +329,11 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Clearing_Items_Should_Fire_LogicalChildren_CollectionChanged() { - var target = new ItemsControl(); + using var app = Start(); var child = new Control(); + var target = CreateTarget(items: new[] { child }); var called = false; - target.Template = GetTemplate(); - target.Items.Add(child); - target.ApplyTemplate(); - ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = e.Action == NotifyCollectionChangedAction.Remove; @@ -392,35 +342,16 @@ namespace Avalonia.Controls.UnitTests Assert.True(called); } - [Fact] - public void Removing_Items_Should_Fire_LogicalChildren_CollectionChanged() - { - var target = new ItemsControl(); - var items = new AvaloniaList { "Foo", "Bar" }; - var called = false; - - target.Template = GetTemplate(); - target.ItemsSource = items; - - ((ILogical)target).LogicalChildren.CollectionChanged += (s, e) => called = true; - - items.Remove("Bar"); - - Assert.False(called); - } - [Fact] public void LogicalChildren_Should_Not_Change_Instance_When_Template_Changed() { - var target = new ItemsControl() - { - Template = GetTemplate(), - }; - + using var app = Start(); + var target = CreateTarget(); var before = ((ILogical)target).LogicalChildren; target.Template = null; - target.Template = GetTemplate(); + target.Template = CreateItemsControlTemplate(); + Layout(target); var after = ((ILogical)target).LogicalChildren; @@ -432,19 +363,13 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Should_Clear_Containers_When_ItemsPresenter_Changes() { - var target = new ItemsControl - { - ItemsSource = new[] { "foo", "bar" }, - Template = GetTemplate(), - }; + using var app = Start(); + var target = CreateTarget(itemsSource: new[] { "foo", "bar" }); + var panel = Assert.IsAssignableFrom(target.Presenter?.Panel); - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - - var panel = target.Presenter.Panel; Assert.Equal(2, panel.Children.Count()); - target.Template = GetTemplate(); + target.Template = CreateItemsControlTemplate(); target.ApplyTemplate(); Assert.Empty(panel.Children); @@ -453,10 +378,8 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Empty_Class_Should_Initially_Be_Applied() { - var target = new ItemsControl() - { - Template = GetTemplate(), - }; + using var app = Start(); + var target = CreateTarget(performLayout: false); Assert.Contains(":empty", target.Classes); } @@ -464,34 +387,26 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Empty_Class_Should_Be_Cleared_When_Items_Added() { - var target = new ItemsControl() - { - Template = GetTemplate(), - ItemsSource = new[] { 1, 2, 3 }, - }; + using var app = Start(); + var target = CreateTarget(items: new[] { 1, 2, 3 }, performLayout: false); Assert.DoesNotContain(":empty", target.Classes); } [Fact] - public void Empty_Class_Should_Be_Set_When_Items_Not_Set() + public void Empty_Class_Should_Be_Cleared_When_ItemsSource_Items_Added() { - var target = new ItemsControl() - { - Template = GetTemplate(), - }; + using var app = Start(); + var target = CreateTarget(itemsSource: new[] { 1, 2, 3 }, performLayout: false); - Assert.Contains(":empty", target.Classes); + Assert.DoesNotContain(":empty", target.Classes); } [Fact] - public void Empty_Class_Should_Be_Set_When_Empty_Collection_Set() + public void Empty_Class_Should_Be_Set_When_ItemsSource_Collection_Cleared() { - var target = new ItemsControl() - { - Template = GetTemplate(), - ItemsSource = new[] { 1, 2, 3 }, - }; + using var app = Start(); + var target = CreateTarget(itemsSource: new[] { 1, 2, 3 }); target.ItemsSource = new int[0]; @@ -499,13 +414,10 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void Item_Count_Should_Be_Set_When_Items_Added() + public void Item_Count_Should_Be_Set_When_ItemsSource_Set() { - var target = new ItemsControl() - { - Template = GetTemplate(), - ItemsSource = new[] { 1, 2, 3 }, - }; + using var app = Start(); + var target = CreateTarget(itemsSource: new[] { 1, 2, 3 }); Assert.Equal(3, target.ItemCount); } @@ -513,13 +425,25 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Item_Count_Should_Be_Set_When_Items_Changed() { + using var app = Start(); var items = new ObservableCollection() { 1, 2, 3 }; + var target = CreateTarget(items: new[] { 1, 2, 3 }); - var target = new ItemsControl() - { - Template = GetTemplate(), - ItemsSource = items, - }; + target.Items.Add(4); + + Assert.Equal(4, target.ItemCount); + + target.Items.Clear(); + + Assert.Equal(0, target.ItemCount); + } + + [Fact] + public void Item_Count_Should_Be_Set_When_ItemsSource_Items_Changed() + { + using var app = Start(); + var items = new ObservableCollection() { 1, 2, 3 }; + var target = CreateTarget(itemsSource: items); items.Add(4); @@ -533,13 +457,9 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Empty_Class_Should_Be_Set_When_Items_Collection_Cleared() { + using var app = Start(); var items = new ObservableCollection() { 1, 2, 3 }; - - var target = new ItemsControl() - { - Template = GetTemplate(), - ItemsSource = items, - }; + var target = CreateTarget(itemsSource: items); items.Clear(); @@ -547,15 +467,11 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void Empty_Class_Should_Not_Be_Set_When_Items_Collection_Count_Increases() + public void Empty_Class_Should_Not_Be_Set_When_ItemsSource_Collection_Count_Increases() { + using var app = Start(); var items = new ObservableCollection() { }; - - var target = new ItemsControl() - { - Template = GetTemplate(), - ItemsSource = items, - }; + var target = CreateTarget(itemsSource: items); items.Add(1); @@ -565,13 +481,9 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Single_Item_Class_Should_Be_Set_When_ItemsSource_Collection_Count_Increases_To_One() { + using var app = Start(); var items = new ObservableCollection() { }; - - var target = new ItemsControl() - { - Template = GetTemplate(), - ItemsSource = items, - }; + var target = CreateTarget(itemsSource: items); items.Add(1); @@ -579,15 +491,11 @@ namespace Avalonia.Controls.UnitTests } [Fact] - public void Empty_Class_Should_Not_Be_Set_When_Items_Collection_Cleared() + public void Empty_Class_Should_Not_Be_Set_When_ItemsSource_Collection_Cleared() { + using var app = Start(); var items = new ObservableCollection() { 1, 2, 3 }; - - var target = new ItemsControl() - { - Template = GetTemplate(), - ItemsSource = items, - }; + var target = CreateTarget(itemsSource: items); items.Clear(); @@ -597,13 +505,9 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Single_Item_Class_Should_Not_Be_Set_When_Items_Collection_Count_Increases_Beyond_One() { + using var app = Start(); var items = new ObservableCollection() { 1 }; - - var target = new ItemsControl() - { - Template = GetTemplate(), - ItemsSource = items, - }; + var target = CreateTarget(itemsSource: items); items.Add(2); @@ -613,6 +517,7 @@ namespace Avalonia.Controls.UnitTests [Fact] public void DataContexts_Should_Be_Correctly_Set() { + using var app = Start(); var items = new object[] { "Foo", @@ -620,22 +525,13 @@ namespace Avalonia.Controls.UnitTests new TextBlock { Text = "Baz" }, new ListBoxItem { Content = "Qux" }, }; - - var target = new ItemsControl - { - Template = GetTemplate(), - DataContext = "Base", - DataTemplates = - { - new FuncDataTemplate((x, __) => new Button { Content = x }) - }, - ItemsSource = items, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - - var dataContexts = target.Presenter.Panel.Children + var dataTemplate = new FuncDataTemplate((x, __) => new Button { Content = x }); + var target = CreateTarget( + dataContext: "Base", + itemsSource: items, + dataTemplates: new[] { dataTemplate }); + var panel = Assert.IsAssignableFrom(target.ItemsPanelRoot); + var dataContexts = panel.Children .Do(x => (x as ContentPresenter)?.UpdateChild()) .Cast() .Select(x => x.DataContext) @@ -649,198 +545,146 @@ namespace Avalonia.Controls.UnitTests [Fact] public void Control_Item_Should_Not_Be_NameScope() { - var items = new object[] - { - new TextBlock(), - }; - - var target = new ItemsControl - { - Template = GetTemplate(), - ItemsSource = items, - }; - - target.ApplyTemplate(); - target.Presenter.ApplyTemplate(); - + using var app = Start(); + var items = new object[] { new TextBlock() }; + var target = CreateTarget(itemsSource: items); var item = target.LogicalChildren[0]; + Assert.Null(NameScope.GetNameScope((TextBlock)item)); } [Fact] public void Focuses_Next_Item_On_Key_Down() { - using (UnitTestApplication.Start(TestServices.RealFocus)) + using var app = Start(); + var items = new object[] { - var items = new object[] - { - new Button(), - new Button(), - }; - - var target = new ItemsControl - { - Template = GetTemplate(), - ItemsSource = items, - }; + new Button(), + new Button(), + }; - var root = new TestRoot { Child = target }; + var target = CreateTarget(itemsSource: items); + GetContainer