Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class CollectionView2 : CollectionView { }
class CarouselView1 : CarouselView { }
class CarouselView2 : CarouselView { }

public static partial class CollectionViewHostBuilderExtentions
public static partial class CollectionViewHostBuilderExtensions
{
/// <summary>
/// Configure the .NET MAUI app to listen for fold-related events
Expand Down
52 changes: 52 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue25671.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:i="clr-namespace:Maui.Controls.Sample.Issues"
x:Class="Maui.Controls.Sample.Issues.Issue25671"
Title="LayoutPassTest">

<i:Issue25671AbsoluteLayout>
<i:Issue25671Grid Padding="0,0,0,100"
BackgroundColor="DarkGray"
RowDefinitions="Auto,*,Auto"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0,0,1,1">

<i:Issue25671ContentView>
<i:Issue25671Label Text="Hello world" Padding="16,8" TextColor="White" />
</i:Issue25671ContentView>

<i:Issue25671CollectionView x:Name="CV" AutomationId="CV" Grid.Row="1">
<i:Issue25671CollectionView.ItemTemplate>
<DataTemplate>
<i:Issue25671ContentView Padding="16">
<i:Issue25671VerticalStackLayout Shadow="{Shadow Radius=16, Brush=Black, Opacity=0.24}"
BackgroundColor="SlateBlue"
Padding="16,8"
Spacing="8">
<i:Issue25671Label Text="{Binding Text}" TextColor="White"/>
<i:Issue25671Image HorizontalOptions="Center">
<i:Issue25671Image.Source>
<FontImageSource Glyph="{Binding Glyph}" Color="White" FontFamily="FA" Size="24"/>
</i:Issue25671Image.Source>
</i:Issue25671Image>
</i:Issue25671VerticalStackLayout>
</i:Issue25671ContentView>
</DataTemplate>
</i:Issue25671CollectionView.ItemTemplate>
</i:Issue25671CollectionView>

<i:Issue25671VerticalStackLayout Grid.Row="2" BackgroundColor="DarkBlue">
<i:Issue25671Button Text="Regenerate items" Padding="16,8" TextColor="White" Clicked="RegenerateItems" AutomationId="RegenerateItems" />
</i:Issue25671VerticalStackLayout>
</i:Issue25671Grid>

<i:Issue25671VerticalStackLayout HeightRequest="100"
BackgroundColor="SlateBlue"
VerticalOptions="End"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0,1,1,1">
<Button Text="Press Me" Clicked="OnClick" TextColor="White" AutomationId="PressMe" />
</i:Issue25671VerticalStackLayout>
</i:Issue25671AbsoluteLayout>
</ContentPage>
191 changes: 191 additions & 0 deletions src/Controls/tests/TestCases.HostApp/Issues/Issue25671.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
using System.Text.RegularExpressions;

namespace Maui.Controls.Sample.Issues;

[Issue(IssueTracker.Github, 25671, "Layout passes should not increase", PlatformAffected.All)]

public partial class Issue25671 : ContentPage
{
public static long MeasurePasses = 0;
public static long ArrangePasses = 0;

private int _regenIndex = 2;
private static readonly string _loremIpsumLongText = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue.";
private static readonly string[] _words = new Regex(@"\w+").Matches(_loremIpsumLongText).Select(m => m.Value).ToArray();

public Issue25671()
{
InitializeComponent();
GenerateItems();
}

void RegenerateItems(object sender, EventArgs args)
{
_regenIndex = (_regenIndex - 1) % 4 + 2;
GenerateItems();
}

void OnClick(object sender, EventArgs args)
{
((Button)sender).Text = $"M: {MeasurePasses}, A: {ArrangePasses}";
}

void GenerateItems()
{
CV.ItemsSource = Enumerable.Range(4, 200).Select(i =>
new
{
Text = string.Join(' ', _words.Take(i % _regenIndex == 0 ? i % _words.Length : (i * 2) % _words.Length)),
Glyph = char.ConvertFromUtf32(0xf127 + i % 10)
});
}
}

#if IOS
// When CV2 is completed and can handle resize of items we can remove this pointer to CV1
internal class Issue25671CollectionView : CollectionView1
#else
public class Issue25671CollectionView : CollectionView
#endif
{
}

public class Issue25671AbsoluteLayout : AbsoluteLayout
{
public static long MeasurePasses = 0;
public static long ArrangePasses = 0;

protected override Size ArrangeOverride(Rect bounds)
{
Interlocked.Increment(ref Issue25671.ArrangePasses);
Interlocked.Increment(ref ArrangePasses);
return base.ArrangeOverride(bounds);
}

protected override Size MeasureOverride(double widthConstraint, double heightConstraint)
{
Interlocked.Increment(ref Issue25671.MeasurePasses);
Interlocked.Increment(ref MeasurePasses);
return base.MeasureOverride(widthConstraint, heightConstraint);
}
}

public class Issue25671VerticalStackLayout : VerticalStackLayout
{
public static long MeasurePasses = 0;
public static long ArrangePasses = 0;

protected override Size ArrangeOverride(Rect bounds)
{
Interlocked.Increment(ref Issue25671.ArrangePasses);
Interlocked.Increment(ref ArrangePasses);
return base.ArrangeOverride(bounds);
}

protected override Size MeasureOverride(double widthConstraint, double heightConstraint)
{
Interlocked.Increment(ref Issue25671.MeasurePasses);
Interlocked.Increment(ref MeasurePasses);
return base.MeasureOverride(widthConstraint, heightConstraint);
}
}

public class Issue25671Grid : Grid
{
public static long MeasurePasses = 0;
public static long ArrangePasses = 0;

protected override Size ArrangeOverride(Rect bounds)
{
Interlocked.Increment(ref Issue25671.ArrangePasses);
Interlocked.Increment(ref ArrangePasses);
return base.ArrangeOverride(bounds);
}

protected override Size MeasureOverride(double widthConstraint, double heightConstraint)
{
Interlocked.Increment(ref Issue25671.MeasurePasses);
Interlocked.Increment(ref MeasurePasses);
return base.MeasureOverride(widthConstraint, heightConstraint);
}
}

public class Issue25671ContentView : ContentView
{
public static long MeasurePasses = 0;
public static long ArrangePasses = 0;

protected override Size ArrangeOverride(Rect bounds)
{
Interlocked.Increment(ref Issue25671.ArrangePasses);
Interlocked.Increment(ref ArrangePasses);
return base.ArrangeOverride(bounds);
}

protected override Size MeasureOverride(double widthConstraint, double heightConstraint)
{
Interlocked.Increment(ref Issue25671.MeasurePasses);
Interlocked.Increment(ref MeasurePasses);
return base.MeasureOverride(widthConstraint, heightConstraint);
}
}

public class Issue25671Label : Label
{
public static long MeasurePasses = 0;
public static long ArrangePasses = 0;

protected override Size ArrangeOverride(Rect bounds)
{
Interlocked.Increment(ref Issue25671.ArrangePasses);
Interlocked.Increment(ref ArrangePasses);
return base.ArrangeOverride(bounds);
}

protected override Size MeasureOverride(double widthConstraint, double heightConstraint)
{
Interlocked.Increment(ref Issue25671.MeasurePasses);
Interlocked.Increment(ref MeasurePasses);
return base.MeasureOverride(widthConstraint, heightConstraint);
}
}

public class Issue25671Button : Button
{
public static long MeasurePasses = 0;
public static long ArrangePasses = 0;

protected override Size ArrangeOverride(Rect bounds)
{
Interlocked.Increment(ref Issue25671.ArrangePasses);
Interlocked.Increment(ref ArrangePasses);
return base.ArrangeOverride(bounds);
}

protected override Size MeasureOverride(double widthConstraint, double heightConstraint)
{
Interlocked.Increment(ref Issue25671.MeasurePasses);
Interlocked.Increment(ref MeasurePasses);
return base.MeasureOverride(widthConstraint, heightConstraint);
}
}

public class Issue25671Image : Image
{
public static long MeasurePasses = 0;
public static long ArrangePasses = 0;

protected override Size ArrangeOverride(Rect bounds)
{
Interlocked.Increment(ref Issue25671.ArrangePasses);
Interlocked.Increment(ref ArrangePasses);
return base.ArrangeOverride(bounds);
}

protected override Size MeasureOverride(double widthConstraint, double heightConstraint)
{
Interlocked.Increment(ref Issue25671.MeasurePasses);
Interlocked.Increment(ref MeasurePasses);
return base.MeasureOverride(widthConstraint, heightConstraint);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#if ANDROID || IOS
using NUnit.Framework;
using NUnit.Framework.Legacy;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues
{
public class Issue25671 : _IssuesUITest
{
public Issue25671(TestDevice testDevice) : base(testDevice)
{
}

public override string Issue => "Layout passes should not increase";

[Test]
[Category(UITestCategories.CollectionView)]
public async Task LayoutPassesShouldNotIncrease()
{
App.WaitForElement("RegenerateItems");

await ScrollDown();
await ScrollDown();
await ScrollDown();
await ScrollUp();
await ScrollUp();

App.Tap("RegenerateItems");

await ScrollDown();
await ScrollDown();
await ScrollUp();
await ScrollUp();

App.Tap("PressMe");
// Text will be in the format "M: 0, A: 0"
var text = App.WaitForElement("PressMe").GetText()!;
// Get measure passes and arrange passes value via regex
var match = System.Text.RegularExpressions.Regex.Match(text, @"M: (\d+), A: (\d+)");
var measurePasses = int.Parse(match.Groups[1].Value);
var arrangePasses = int.Parse(match.Groups[2].Value);

#if IOS
const int maxMeasurePasses = 525;
const int maxArrangePasses = 308;
#elif ANDROID
const int maxMeasurePasses = 353;
const int maxArrangePasses = 337;
#endif

var logMessage = @$"Measure passes: {measurePasses}, Arrange passes: {arrangePasses}";
TestContext.WriteLine(logMessage);

// Write the log to a file and attach it to the test results for ADO
var logFile = Path.Combine(Path.GetTempPath(), "LayoutPasses.log");
File.WriteAllText(logFile, logMessage);
TestContext.AddTestAttachment(logFile, "LayoutPasses.log");

// Then assert that the measure passes and arrange passes are less than the expected values.
// Let's give a 5% margin of error.
ClassicAssert.LessOrEqual(measurePasses, maxMeasurePasses * 1.05);
ClassicAssert.LessOrEqual(arrangePasses, maxArrangePasses * 1.05);
}

async Task ScrollDown()
{
await Task.Delay(50);
App.ScrollDown("CV", swipePercentage: 0.5D, swipeSpeed: 1000);
await Task.Delay(50);
}

async Task ScrollUp()
{
await Task.Delay(50);
App.ScrollUp("CV", swipePercentage: 0.5D, swipeSpeed: 1000);
await Task.Delay(50);
}
}
}
#endif
Loading