Skip to content

Commit

Permalink
[tests] test a lot more things in MemoryTests.cs
Browse files Browse the repository at this point in the history
This expands the tests to cover more controls and areas.

* Add test cases for more controls:

  * `Ellipse`
  * `Grid`
  * `Path`
  * `Line`
  * `Path`
  * `RadioButton`
  * `Rectangle`
  * `RoundRectangle`

* Expand tests for a couple controls:

    * `Border` has a `StrokeShape`
    * Any `TemplatedView` gets a `ControlTemplate`

* Re-enable `ListView` for Android

This should work now after merging:

* dotnet/android#8900
* dotnet#23120

* Add a complicated test case for `BindableLayout`

Similar to the case at:

* dotnet#23199
  • Loading branch information
jonathanpeppers committed Jun 27, 2024
1 parent 3e3cd67 commit 93e5e22
Showing 1 changed file with 87 additions and 5 deletions.
92 changes: 87 additions & 5 deletions src/Controls/tests/DeviceTests/Memory/MemoryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Microsoft.Maui.Controls.Handlers.Items;
using Microsoft.Maui.Controls.Platform;
using Microsoft.Maui.Controls.Shapes;
using Microsoft.Maui.DeviceTests.Stubs;
using Microsoft.Maui.Handlers;
using Microsoft.Maui.Hosting;
using Xunit;
Expand All @@ -30,13 +31,15 @@ void SetupBuilder()
handlers.AddHandler<CollectionView, CollectionViewHandler>();
handlers.AddHandler<CheckBox, CheckBoxHandler>();
handlers.AddHandler<DatePicker, DatePickerHandler>();
handlers.AddHandler<Shape, ShapeViewHandler>();
handlers.AddHandler<Entry, EntryHandler>();
handlers.AddHandler<EntryCell, EntryCellRenderer>();
handlers.AddHandler<Editor, EditorHandler>();
handlers.AddHandler<Frame, FrameRenderer>();
handlers.AddHandler<GraphicsView, GraphicsViewHandler>();
handlers.AddHandler<Label, LabelHandler>();
handlers.AddHandler<ListView, ListViewRenderer>();
handlers.AddHandler<Layout, LayoutHandler>();
handlers.AddHandler<Picker, PickerHandler>();
handlers.AddHandler<Polygon, PolygonHandler>();
handlers.AddHandler<Polyline, PolylineHandler>();
Expand All @@ -45,6 +48,7 @@ void SetupBuilder()
handlers.AddHandler<ImageButton, ImageButtonHandler>();
handlers.AddHandler<ImageCell, ImageCellRenderer>();
handlers.AddHandler<IndicatorView, IndicatorViewHandler>();
handlers.AddHandler<RadioButton, RadioButtonHandler>();
handlers.AddHandler<RefreshView, RefreshViewHandler>();
handlers.AddHandler<IScrollView, ScrollViewHandler>();
handlers.AddHandler<SearchBar, SearchBarHandler>();
Expand Down Expand Up @@ -131,19 +135,26 @@ public async Task PagesDoNotLeak(Type type)
[InlineData(typeof(ContentView))]
[InlineData(typeof(CheckBox))]
[InlineData(typeof(DatePicker))]
[InlineData(typeof(Ellipse))]
[InlineData(typeof(Entry))]
[InlineData(typeof(Editor))]
[InlineData(typeof(Frame))]
[InlineData(typeof(GraphicsView))]
[InlineData(typeof(Grid))]
[InlineData(typeof(Image))]
[InlineData(typeof(ImageButton))]
[InlineData(typeof(IndicatorView))]
[InlineData(typeof(Line))]
[InlineData(typeof(Label))]
[InlineData(typeof(ListView))]
[InlineData(typeof(Path))]
[InlineData(typeof(Picker))]
[InlineData(typeof(Polygon))]
[InlineData(typeof(Polyline))]
[InlineData(typeof(RadioButton))]
[InlineData(typeof(Rectangle))]
[InlineData(typeof(RefreshView))]
[InlineData(typeof(RoundRectangle))]
[InlineData(typeof(ScrollView))]
[InlineData(typeof(SearchBar))]
[InlineData(typeof(Slider))]
Expand All @@ -159,10 +170,6 @@ public async Task HandlerDoesNotLeak(Type type)
SetupBuilder();

#if ANDROID
// TODO: fixing upstream at https://github.com/xamarin/xamarin-android/pull/8900
if (type == typeof(ListView))
return;

// NOTE: skip certain controls on older Android devices
if (type == typeof (DatePicker) && !OperatingSystem.IsAndroidVersionAtLeast(30))
return;
Expand All @@ -185,7 +192,12 @@ public async Task HandlerDoesNotLeak(Type type)
var layout = new Grid();
var view = (View)Activator.CreateInstance(type);
layout.Add(view);
if (view is ContentView content)
if (view is Border border)
{
border.StrokeShape = new RoundRectangle { CornerRadius = new CornerRadius(10) };
border.Content = new Label();
}
else if (view is ContentView content)
{
content.Content = new Label();
}
Expand Down Expand Up @@ -214,6 +226,15 @@ public async Task HandlerDoesNotLeak(Type type)
webView.Source = new HtmlWebViewSource { Html = "<p>hi</p>" };
await Task.Delay(1000);
}
else if (view is TemplatedView templated)
{
templated.ControlTemplate = new ControlTemplate(() =>
new Border
{
StrokeShape = new RoundRectangle { CornerRadius = new CornerRadius(10) },
Content = new Grid { Children = { new Ellipse(), new ContentPresenter() } }
});
}
var handler = CreateHandler<LayoutHandler>(layout);
viewReference = new WeakReference(view);
handlerReference = new WeakReference(view.Handler);
Expand Down Expand Up @@ -325,6 +346,67 @@ public async Task CellsDoNotLeak(Type type)
await AssertionExtensions.WaitForGC(references.ToArray());
}

[Fact("BindableLayout Does Not Leak")]
public async Task BindableLayoutDoesNotLeak()
{
SetupBuilder();

var references = new List<WeakReference>();
var observable = new ObservableCollection<object>
{
new { Name = "One" },
new { Name = "Two" },
new { Name = "Three" },
};

var layout = new VerticalStackLayout();

{
BindableLayout.SetItemsSource(layout, observable);
BindableLayout.SetItemTemplate(layout, new DataTemplate(() =>
{
var radio = new RadioButton
{
ControlTemplate = new ControlTemplate(() =>
{
var radio = new RadioButton
{
ControlTemplate = new ControlTemplate(() =>
{
var ellipse = new Ellipse();
references.Add(new(ellipse));
return new HorizontalStackLayout
{
Children =
{
ellipse,
new ContentPresenter(),
}
};
})
};
radio.SetBinding(RadioButton.ContentProperty, "Name");
return radio;
})
};
radio.SetBinding(RadioButton.ContentProperty, "Name");
return radio;
}));
var page = new ContentPage { Content = layout };
await CreateHandlerAndAddToWindow<WindowHandlerStub>(new Window(page), async _ =>
{
await OnLoadedAsync(page);
BindableLayout.SetItemsSource(layout, new ObservableCollection<object>(observable));
page.Content = null;
});
}

// 6 Ellipses total: first 3 should not leak, last 3 should still be in the layout & alive
Assert.Equal(6, references.Count);
await AssertionExtensions.WaitForGC(references[0], references[1], references[2]);
}

#if IOS
[Fact]
public async Task ResignFirstResponderTouchGestureRecognizer()
Expand Down

0 comments on commit 93e5e22

Please sign in to comment.