From 93e5e221eabebc8d524b1efd7b353adc52de0113 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 27 Jun 2024 11:51:52 -0500 Subject: [PATCH] [tests] test a lot more things in `MemoryTests.cs` 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: * https://github.com/dotnet/android/pull/8900 * https://github.com/dotnet/maui/pull/23120 * Add a complicated test case for `BindableLayout` Similar to the case at: * https://github.com/dotnet/maui/issues/23199 --- .../tests/DeviceTests/Memory/MemoryTests.cs | 92 ++++++++++++++++++- 1 file changed, 87 insertions(+), 5 deletions(-) diff --git a/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs b/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs index 87b47ce0f66a..189efe05d88e 100644 --- a/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs +++ b/src/Controls/tests/DeviceTests/Memory/MemoryTests.cs @@ -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; @@ -30,6 +31,7 @@ void SetupBuilder() handlers.AddHandler(); handlers.AddHandler(); handlers.AddHandler(); + handlers.AddHandler(); handlers.AddHandler(); handlers.AddHandler(); handlers.AddHandler(); @@ -37,6 +39,7 @@ void SetupBuilder() handlers.AddHandler(); handlers.AddHandler(); handlers.AddHandler(); + handlers.AddHandler(); handlers.AddHandler(); handlers.AddHandler(); handlers.AddHandler(); @@ -45,6 +48,7 @@ void SetupBuilder() handlers.AddHandler(); handlers.AddHandler(); handlers.AddHandler(); + handlers.AddHandler(); handlers.AddHandler(); handlers.AddHandler(); handlers.AddHandler(); @@ -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))] @@ -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; @@ -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(); } @@ -214,6 +226,15 @@ public async Task HandlerDoesNotLeak(Type type) webView.Source = new HtmlWebViewSource { Html = "

hi

" }; 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(layout); viewReference = new WeakReference(view); handlerReference = new WeakReference(view.Handler); @@ -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(); + var observable = new ObservableCollection + { + 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(new Window(page), async _ => + { + await OnLoadedAsync(page); + BindableLayout.SetItemsSource(layout, new ObservableCollection(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()