diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ListViewBase.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ListViewBase.cs index eb3a146c2933..f4e05241bdad 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ListViewBase.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ListViewBase.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Drawing; using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; @@ -37,6 +38,7 @@ #endif using static Private.Infrastructure.TestServices; +using Point = Windows.Foundation.Point; namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Xaml_Controls { @@ -1419,7 +1421,7 @@ public async Task When_Large_List_Scroll_To_End_Then_Back_Up_And_First_Item() await Task.Delay(200); await WindowHelper.WaitForIdle(); - ScrollTo(list, 5); // Scroll to end + ScrollTo(list, 5); // Scroll back up await Task.Delay(200); await WindowHelper.WaitForIdle(); @@ -1436,6 +1438,373 @@ public async Task When_Large_List_Scroll_To_End_Then_Back_Up_And_First_Item() } } + [TestMethod] + [RunsOnUIThread] +#if __IOS__ || __ANDROID__ + [Ignore("Disabled because of animated scrolling, even when explicitly requested.")] +#elif __WASM__ + [Ignore("Flaky in CI.")] +#endif + public async Task When_Large_List_Scroll_To_End_Then_Back_Up_And_First_Item2() + { + var container = new Grid { Height = 500, Width = 100 }; + + var list = new ListView + { + ItemContainerStyle = NoSpaceContainerStyle, + ItemTemplate = new DataTemplate(() => + { + var tb = new TextBlock(); + tb.SetBinding(TextBlock.TextProperty, new Binding()); + var border = new Border() + { + Height = 50, + Child = tb + }; + + return border; + }) + }; + container.Children.Add(list); + + var source = new List(); + + var random = new Random(42); + string RandomString(int length) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + return new string(Enumerable.Repeat(chars, length) + .Select(s => s[random.Next(s.Length)]).ToArray()); + } + + for (var i = 0; i < 100; i++) + { + source.Add(RandomString(5)); + } + list.ItemsSource = source; + + WindowHelper.WindowContent = container; + await WindowHelper.WaitForIdle(); + + ScrollTo(list, 1000000); // Scroll to end + + await Task.Delay(200); + await WindowHelper.WaitForIdle(); + + ScrollTo(list, 50); // scroll back up but not all the way + ScrollTo(list, 0); + + await Task.Delay(200); + await WindowHelper.WaitForIdle(); + + var firstContainer = (FrameworkElement)list.ContainerFromIndex(0); + + firstContainer.Should().NotBeNull(); + LayoutInformation.GetLayoutSlot(firstContainer).Y.Should().BeLessOrEqualTo(0); + + var secondContainer = (FrameworkElement)list.ContainerFromIndex(1); + + secondContainer.Should().NotBeNull(); + LayoutInformation.GetLayoutSlot(secondContainer).Y.Should().Be(50); + } + + [TestMethod] + [RunsOnUIThread] +#if !__SKIA__ + [Ignore("InputInjector is only supported on skia")] +#endif + public async Task When_Large_List_Scroll_To_End_Then_Back_Up_TryClick() + { + var container = new Grid { Height = 500, Width = 100 }; + + var list = new ListView + { + ItemTemplate = new DataTemplate(() => + { + + var tb = new TextBlock(); + tb.SetBinding(TextBlock.TextProperty, new Binding()); + var border = new Border() + { + Height = 50, + Child = tb + }; + + return border; + }) + }; + container.Children.Add(list); + + var source = new List(); + + var random = new Random(42); + string RandomString(int length) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + return new string(Enumerable.Repeat(chars, length) + .Select(s => s[random.Next(s.Length)]).ToArray()); + } + + for (var i = 0; i < 100; i++) + { + source.Add(RandomString(5)); + } + list.ItemsSource = source; + + WindowHelper.WindowContent = container; + await WindowHelper.WaitForIdle(); + + var injector = InputInjector.TryCreate() ?? throw new InvalidOperationException("Failed to init the InputInjector"); + var mouse = injector.GetMouse(); + + var bounds = list.GetAbsoluteBounds(); + mouse.MoveTo(new Point(bounds.GetMidX(), bounds.Top + 10)); + mouse.Press(); + mouse.Release(); + + await Task.Delay(200); + await WindowHelper.WaitForIdle(); + + // This problem is incredibly difficult to reproduce. These are the exact + // mouse wheel deltas that my trackpad produced when I reproduced this manually. + var deltas = new[] + { + -266, + -393, + -626, + -730, + -410, + -644, + -666, + -328, + -584, + -671, + -535, + -250, + -460, + -514, + -538, + -382, + -692, + -342, + -557, + -331, + -540, + -320, + -523, + -385, + -428, + -397, + -388, + -143, + -382, + -370, + -366, + -358, + -566, + -128, + -545, + -121, + -540, + -97, + -536, + -55, + -476, + -246, + -344, + -203, + -311, + -190, + -295, + -225, + -234, + -86, + -209, + -26, + -389, + -189, + -329, + -57, + -282, + -130, + -205, + -108, + -183, + -106, + -172, + -102, + -86, + -75, + -108, + -112, + -64, + -24, + -20, + -20, + -13, + -11, + -7, + -4, + -5, + -2, + 231, + 122, + 164, + 266, + 887, + 167, + 565, + 434, + 382, + 372, + 344, + 170, + 269, + 547, + 158, + 272, + 434, + 168, + 253, + 386, + 154, + 269, + 244, + 238, + 399, + 91, + 374, + 153, + 282, + 143, + 225, + 194, + 60, + 183, + 154, + 262, + 60, + 249, + 55, + 232, + 46, + 168, + 37, + 128, + 55, + 82, + 37, + 68, + 27, + 35, + 27, + 17, + 13, + 12, + 2, + 6, + 3, + 2, + }; + + foreach (var delta in deltas.Where(i => i < 0)) + { + mouse.Wheel(delta); + } + + await Task.Delay(200); + await WindowHelper.WaitForIdle(); + + mouse.MoveTo(new Point(bounds.GetMidX(), bounds.Top + 220)); + mouse.Press(); + mouse.Release(); + + await Task.Delay(200); + await WindowHelper.WaitForIdle(); + + foreach (var delta in deltas.Where(i => i > 0)) + { + mouse.Wheel(delta); + await WindowHelper.WaitForIdle(); + } + + mouse.MoveTo(new Point(bounds.GetMidX(), bounds.Top + 10)); + mouse.Press(); + mouse.Release(); + + await Task.Delay(200); + await WindowHelper.WaitForIdle(); + + mouse.MoveTo(new Point(bounds.GetMidX(), bounds.Top + 60)); + mouse.Press(); + mouse.Release(); + + await Task.Delay(200); + await WindowHelper.WaitForIdle(); + + // The second container should be selected and nothing else. + // Trying to check for this with references could be misleading, + // since this is originally a virtualization issue and references + // could be to different things than those shown on the screen. + var si = await UITestHelper.ScreenShot(list, true); + ImageAssert.HasColorAt(si, 70, 65, Colors.FromARGB("#66AEE7")); // selected + + // check starting from below the second item that nothing looks selected or hovered + ImageAssert.DoesNotHaveColorInRectangle(si, new Rectangle(100, 110, si.Width - 100, si.Height - 110), Colors.FromARGB("#66AEE7")); // selected + ImageAssert.DoesNotHaveColorInRectangle(si, new Rectangle(100, 110, si.Width - 100, si.Height - 110), Colors.FromARGB("#FFE6E6E6")); // hovered + } + + [TestMethod] + [RunsOnUIThread] +#if !__CROSSRUNTIME__ + [Ignore("Native listviews differ in virtualization mechanics")] +#endif + public async Task ListView_ObservableCollection_Creation_Count() + { + var SUT = new ListView_ObservableCollection_CreationCount(); + + WindowHelper.WindowContent = SUT; + await WindowHelper.WaitForIdle(); + + await AdvanceAutomation("Added"); + await AdvanceAutomation("Scrolled1"); + + var expectedTemplateCreationCount = GetTemplateCreationCount(); + //var expectedTemplateBindCount = GetTemplateBindCount(); // For some reason WASM performs extra bindings on scrolling + var expectedContainerCreationCount = GetContainerCreationCount(); + + await AdvanceAutomation("Scrolled2"); + + Assert.AreEqual(expectedTemplateCreationCount, GetTemplateCreationCount()); + Assert.AreEqual(expectedContainerCreationCount, GetContainerCreationCount()); + + var expectedTemplateBindCount = GetTemplateBindCount(); + + await AdvanceAutomation("Added above"); + + Assert.AreEqual(expectedTemplateCreationCount, GetTemplateCreationCount()); + Assert.AreEqual(expectedContainerCreationCount, GetContainerCreationCount()); + Assert.AreEqual(expectedTemplateBindCount, GetTemplateBindCount()); // Note: this doesn't actually seem to be the case on Windows - the bind count increases for some reason + + await AdvanceAutomation("Removed above"); + + Assert.AreEqual(expectedTemplateCreationCount, GetTemplateCreationCount()); + Assert.AreEqual(expectedContainerCreationCount, GetContainerCreationCount()); + Assert.AreEqual(expectedTemplateBindCount, GetTemplateBindCount()); + + int GetTemplateCreationCount() => int.Parse(((TextBlock)SUT.FindName("CreationCountText")).Text); + int GetTemplateBindCount() => int.Parse(((TextBlock)SUT.FindName("BindCountText")).Text); + int GetContainerCreationCount() => int.Parse(((TextBlock)SUT.FindName("CreationCount2Text")).Text); + + async Task AdvanceAutomation(string automationStep) + { + var button = (Button)SUT.FindName("AutomateButton"); + button.RaiseClick(); + await WindowHelper.WaitFor(() => ((TextBlock)SUT.FindName("AutomationStepTextBlock")).Text == automationStep); + await WindowHelper.WaitForIdle(); + } + } + [TestMethod] [RunsOnUIThread] #if __IOS__ || __ANDROID__ diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/ListViewPages/ListView_ObservableCollection_CreationCount.xaml b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/ListViewPages/ListView_ObservableCollection_CreationCount.xaml new file mode 100644 index 000000000000..71a4f8726020 --- /dev/null +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/ListViewPages/ListView_ObservableCollection_CreationCount.xaml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +