Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Port new Layout Tests #19308

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System;
using Windows.Foundation;

using Microsoft/* UWP don't rename */.UI.Xaml.Controls;

namespace Microsoft.UI.Xaml.Tests.MUXControls.ApiTests.RepeaterTests.Common.Mocks;

partial class MockStackLayout : StackLayout
{
public Func<Size, VirtualizingLayoutContext, Size> MeasureLayoutFunc { get; set; }
public Func<Size, VirtualizingLayoutContext, Size> ArrangeLayoutFunc { get; set; }

public new void InvalidateMeasure()
{
base.InvalidateMeasure();
}

protected internal override Size MeasureOverride(VirtualizingLayoutContext context, Size availableSize)
{
return MeasureLayoutFunc != null ? MeasureLayoutFunc(availableSize, context) : default(Size);
}

protected internal override Size ArrangeOverride(VirtualizingLayoutContext context, Size finalSize)
{
return ArrangeLayoutFunc != null ? ArrangeLayoutFunc(finalSize, context) : default(Size);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
using VirtualizingLayoutContext = Microsoft/* UWP don't rename */.UI.Xaml.Controls.VirtualizingLayoutContext;
using ElementRealizationOptions = Microsoft/* UWP don't rename */.UI.Xaml.Controls.ElementRealizationOptions;
using LayoutContext = Microsoft/* UWP don't rename */.UI.Xaml.Controls.LayoutContext;
using UniformGridLayout = Microsoft/* UWP don't rename */.UI.Xaml.Controls.UniformGridLayout;
using Microsoft.UI.Xaml.Media;
using Private.Infrastructure;
using System.Threading.Tasks;

namespace Microsoft.UI.Xaml.Tests.MUXControls.ApiTests.RepeaterTests
{
Expand Down Expand Up @@ -195,38 +199,6 @@ public void ValidateNonVirtualLayoutDoesNotGetMeasuredForViewportChanges()
});
}

[TestMethod]
[Ignore("UNO: Test does not pass yet with Uno https://github.com/unoplatform/uno/issues/4529")]
public void ValidateStackLayoutDoesNotRetainIncorrectMinorWidth()
{
RunOnUIThread.Execute(() =>
{
var repeater = new ItemsRepeater()
{
ItemsSource = Enumerable.Range(0, 1)
};

Content = new ScrollViewer()
{
Content = repeater,
Width = 400,
};

Content.UpdateLayout();

// Measure with large width.
repeater.Measure(new Size(600, 100));
Verify.AreEqual(600, repeater.DesiredSize.Width);
// Measure with smaller width again before arrange.
// StackLayout has to pick up the smaller width for its extent.
repeater.Measure(new Size(300, 100));
Verify.AreEqual(300, repeater.DesiredSize.Width);

Content.UpdateLayout();
Verify.AreEqual(400, repeater.ActualWidth);
});
}

[TestMethod]
#if __IOS__
[Ignore("UNO: Test does not pass yet with Uno (causes infinite layout cycle) https://github.com/unoplatform/uno/issues/4529")]
Expand Down Expand Up @@ -264,6 +236,253 @@ public void ValidateStackLayoutDisabledVirtualizationWithItemsRepeater()
});
}

[TestMethod]
public void VerifyStackLayoutCycleShortcut()
{
RunOnUIThread.Execute(() =>
{
int measureCount = 0;
int arrangeCount = 0;
var repeater = new ItemsRepeater();
var mockStackLayout = new MockStackLayout();

mockStackLayout.MeasureLayoutFunc = (size, context) =>
{
// Simulating variable sized children that cause
// the ItemsRepeater's layout to not settle.
// This would normally cause a layout cycle but the use
// of ItemsRepeater::m_stackLayoutMeasureCounter avoids it.
mockStackLayout.InvalidateMeasure();
measureCount++;
return new Size(100, 200 + measureCount);
};
mockStackLayout.ArrangeLayoutFunc = (size, context) =>
{
arrangeCount++;
return new Size(100, 200 + arrangeCount);
};

repeater.Layout = mockStackLayout;
repeater.ItemsSource = Enumerable.Range(0, 10);
repeater.ItemTemplate = GetDataTemplate("<Button Content='{Binding}' Height='200'/>");

Content = repeater;
Content.UpdateLayout();
});
}

[TestMethod]
[Ignore("Fails, itemsRepeaterOriginPoint.X is 0 at all times - https://github.com/unoplatform/uno/issues/8261")]
public async Task VerifyStackLayoutAlignment()
{
for (int horizontalAlignment = 0; horizontalAlignment <= 3; horizontalAlignment++)
{
Border border = null;
ItemsRepeater itemsRepeater = null;

RunOnUIThread.Execute(() =>
{
itemsRepeater = new ItemsRepeater()
{
HorizontalAlignment = (HorizontalAlignment)horizontalAlignment,
ItemsSource = Enumerable.Range(0, 2)
};

border = new Border()
{
Background = new SolidColorBrush(Colors.Azure),
Width = 400,
Height = 400,
Child = itemsRepeater
};

Content = border;
});

await TestServices.WindowHelper.WaitForIdle();

RunOnUIThread.Execute(() =>
{
Log.Comment($"ItemsRepeater.HorizontalAlignment set to {itemsRepeater.HorizontalAlignment}");
Log.Comment($"ItemsRepeater actual size: {itemsRepeater.ActualWidth}, {itemsRepeater.ActualHeight}");

Verify.AreEqual((HorizontalAlignment)horizontalAlignment, itemsRepeater.HorizontalAlignment);

GeneralTransform gt = itemsRepeater.TransformToVisual(border);
Point itemsRepeaterOriginPoint = new Point();
itemsRepeaterOriginPoint = gt.TransformPoint(itemsRepeaterOriginPoint);
Log.Comment($"ItemsRepeater position: {itemsRepeaterOriginPoint}");

for (int itemIndex = 0; itemIndex <= 1; itemIndex++)
{
FrameworkElement itemElement = itemsRepeater.TryGetElement(itemIndex) as FrameworkElement;
Verify.IsNotNull(itemElement);
var layoutBounds = LayoutInformation.GetLayoutSlot(itemElement);
Log.Comment($"ItemsRepeater child #{itemIndex} layout bounds: {layoutBounds}");
}

switch (itemsRepeater.HorizontalAlignment)
{
case HorizontalAlignment.Left:
case HorizontalAlignment.Stretch:
Verify.AreEqual(0, itemsRepeaterOriginPoint.X);
break;
case HorizontalAlignment.Center:
Verify.IsTrue(itemsRepeaterOriginPoint.X > 0);
Verify.AreEqual((border.ActualWidth - itemsRepeater.ActualWidth) / 2, itemsRepeaterOriginPoint.X);
break;
case HorizontalAlignment.Right:
Verify.IsTrue(itemsRepeaterOriginPoint.X > 0);
Verify.AreEqual(border.ActualWidth - itemsRepeater.ActualWidth, itemsRepeaterOriginPoint.X);
break;
}
});
}
}

[TestMethod]
public async Task VerifyUniformGridLayoutDoesntCrashWhenTryingToScrollToEnd()
{
ItemsRepeater repeater = null;
ScrollViewer scrollViewer = null;
RunOnUIThread.Execute(() =>
{
repeater = new ItemsRepeater
{
ItemsSource = Enumerable.Range(0, 1000).Select(i => new Border
{
Background = new SolidColorBrush(Colors.Blue),
Child = new TextBlock { Text = "#" + i }
}).ToArray(),
Layout = new UniformGridLayout
{
MinItemWidth = 100,
MinItemHeight = 40,
MinRowSpacing = 10,
MinColumnSpacing = 10
}
};
scrollViewer = new ScrollViewer { Content = repeater };
Content = scrollViewer;
});

await TestServices.WindowHelper.WaitForIdle();

RunOnUIThread.Execute(() =>
{
scrollViewer.ChangeView(0, repeater.ActualHeight, null);
});

await TestServices.WindowHelper.WaitForIdle();

RunOnUIThread.Execute(() =>
{
scrollViewer.ChangeView(0, 0, null);
});

await TestServices.WindowHelper.WaitForIdle();

// The test guards against an app crash, so this is enough to verify
Verify.IsTrue(true);
}

[TestMethod]
#if !HAS_UNO_WINUI
[Ignore("Fails on UWP")]
#endif
public async Task VerifyUniformGridLayoutDoesntHangWhenTryingToScrollToStart()
{
ItemsRepeater itemsRepeater = null;
ScrollViewer scrollViewer = null;
ManualResetEvent viewChanged = new ManualResetEvent(false);

RunOnUIThread.Execute(() =>
{
Log.Comment("Setting up UI.");

itemsRepeater = new ItemsRepeater
{
ItemsSource = Enumerable.Range(0, 10).Select(_ => Enumerable.Range(1, 6).ToList()).ToList()
};
itemsRepeater.ItemTemplate = XamlReader.Load(
@"<DataTemplate xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'>
<ItemsRepeater ItemsSource='{Binding}'>
<ItemsRepeater.Layout>
<UniformGridLayout
Orientation='Horizontal'
MinItemWidth='150.0'
MaximumRowsOrColumns='3'
MinRowSpacing='4'
MinColumnSpacing='4'
MinItemHeight='150.0'
ItemsStretch='Fill'/>
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<TextBlock Text='{Binding}'/>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</DataTemplate>") as DataTemplate;

scrollViewer = new ScrollViewer
{
Width = 500.0,
Height = 400.0,
Content = itemsRepeater
};
scrollViewer.ViewChanged += (sender, args) =>
{
Log.Comment($"ScrollViewer.ViewChanged raised for VerticalOffset '{scrollViewer.VerticalOffset}'.");

if (!args.IsIntermediate)
{
Log.Comment("ScrollViewer.ChangeView completed.");

viewChanged.Set();
}
};

Content = scrollViewer;
Content.UpdateLayout();
});

await TestServices.WindowHelper.WaitForIdle();

Log.Comment("Scrolling to the bottom in 16 jumps...");

for (int i = 1; i <= 16; i++)
{
RunOnUIThread.Execute(() =>
{
Log.Comment($"Jumping to VerticalOffset '{scrollViewer.ScrollableHeight * i / 16}'.");

scrollViewer.ChangeView(null, scrollViewer.ScrollableHeight * i / 16, null, true);
});

Verify.IsTrue(viewChanged.WaitOne());
viewChanged.Reset();
await TestServices.WindowHelper.WaitForIdle();
}

Log.Comment("Scrolling to the top in 16 jumps...");

for (int i = 15; i >= 0; i--)
{
RunOnUIThread.Execute(() =>
{
Log.Comment($"Jumping to VerticalOffset '{scrollViewer.ScrollableHeight * i / 16}'.");

scrollViewer.ChangeView(null, scrollViewer.ScrollableHeight * i / 16, null, true);
});

Verify.IsTrue(viewChanged.WaitOne());
viewChanged.Reset();
await TestServices.WindowHelper.WaitForIdle();
}
}


private ItemsRepeaterScrollHost CreateAndInitializeRepeater(
object itemsSource,
VirtualizingLayout layout,
Expand Down