diff --git a/src/CommunityToolkit.Maui.UnitTests/BaseHandlerTest.cs b/src/CommunityToolkit.Maui.UnitTests/BaseHandlerTest.cs index 5c0cd45d84..87ae78be4f 100644 --- a/src/CommunityToolkit.Maui.UnitTests/BaseHandlerTest.cs +++ b/src/CommunityToolkit.Maui.UnitTests/BaseHandlerTest.cs @@ -81,7 +81,9 @@ static void InitializeServicesAndSetMockApplication(out IServiceProvider service var mockPopup = new MockSelfClosingPopup(mockPageViewModel, new()); PopupService.AddPopup(mockPopup, mockPageViewModel, appBuilder.Services, ServiceLifetime.Transient); + appBuilder.Services.AddTransientPopup(); + appBuilder.Services.AddTransient(); #endregion var mauiApp = appBuilder.Build(); diff --git a/src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs b/src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs index cef2f4423c..5c1099f499 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Extensions/PopupExtensionsTests.cs @@ -946,6 +946,21 @@ public async Task ShowPopupAsyncWithView_Shell_ShouldValidateProperBindingContex Assert.Equal(shellParameterViewModelTextValue, view.BindingContext.Text); } + [Fact(Timeout = (int)TestDuration.Medium)] + public async Task ShowPopupAsync_ShouldSuccessfullyCompleteAndReturnResultUnderHeavyGarbageCollection() + { + // Arrange + var mockPopup = ServiceProvider.GetRequiredService(); + var selfClosingPopup = ServiceProvider.GetRequiredService() ?? throw new InvalidOperationException(); + + // Act + var result = await navigation.ShowPopupAsync(selfClosingPopup, PopupOptions.Empty, TestContext.Current.CancellationToken); + + // Assert + Assert.Same(mockPopup.Result, result.Result); + Assert.False(result.WasDismissedByTappingOutsideOfPopup); + } + [Fact(Timeout = (int)TestDuration.Medium)] public async Task ShowPopupAsync_ShouldReturnResultOnceClosed() { @@ -1430,7 +1445,7 @@ public async Task ClosePopupAsyncT_ShouldClosePopupUsingPageAndReturnResult() Assert.Equal(expectedResult, popupResult.Result); Assert.False(popupResult.WasDismissedByTappingOutsideOfPopup); } - + [Fact(Timeout = (int)TestDuration.Short)] public async Task ShowPopupAsync_TaskShouldCompleteWhenCloseAsyncIsCalled() { @@ -1460,7 +1475,7 @@ public async Task ShowPopupAsync_TaskShouldCompleteWhenCloseAsyncIsCalled() Assert.False(popupResult.WasDismissedByTappingOutsideOfPopup); } - static TapGestureRecognizer GetTapOutsideGestureRecognizer(PopupPage popupPage) => + static TapGestureRecognizer GetTapOutsideGestureRecognizer(PopupPage popupPage) => (TapGestureRecognizer)popupPage.Content.Children.OfType().Single().GestureRecognizers[0]; } diff --git a/src/CommunityToolkit.Maui.UnitTests/Services/PopupServiceTests.cs b/src/CommunityToolkit.Maui.UnitTests/Services/PopupServiceTests.cs index 41f929d100..fb0b29bba6 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Services/PopupServiceTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Services/PopupServiceTests.cs @@ -522,7 +522,9 @@ public async Task ClosePopupAsyncT_ShouldClosePopupUsingPageAndReturnResult() } } -sealed class MockSelfClosingPopup : Popup, IQueryAttributable +class GarbageCollectionHeavySelfClosingPopup(MockPageViewModel viewModel, object? result = null) : MockSelfClosingPopup(viewModel, result); + +class MockSelfClosingPopup : Popup, IQueryAttributable { public const int ExpectedResult = 2; @@ -548,7 +550,9 @@ async void HandleTick(object? sender, EventArgs e) timer.Tick -= HandleTick; try { + GC.Collect(); await CloseAsync(Result); + GC.Collect(); } catch (InvalidOperationException) { diff --git a/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupPageTests.cs b/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupPageTests.cs index f75256bbe1..14c1ebdd0e 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupPageTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupPageTests.cs @@ -230,32 +230,32 @@ public void TapGestureRecognizer_VerifyCanBeDismissedByTappingOutsideOfPopup_Sho // Assert Assert.True(tapGestureRecognizer.Command?.CanExecute(null)); - + // Act view.CanBeDismissedByTappingOutsideOfPopup = false; popupOptions.CanBeDismissedByTappingOutsideOfPopup = false; - + // Assert Assert.False(tapGestureRecognizer.Command?.CanExecute(null)); - + // Act view.CanBeDismissedByTappingOutsideOfPopup = true; popupOptions.CanBeDismissedByTappingOutsideOfPopup = false; - + // Assert Assert.False(tapGestureRecognizer.Command?.CanExecute(null)); - + // Act view.CanBeDismissedByTappingOutsideOfPopup = false; popupOptions.CanBeDismissedByTappingOutsideOfPopup = true; - + // Assert Assert.False(tapGestureRecognizer.Command?.CanExecute(null)); - + // Act view.CanBeDismissedByTappingOutsideOfPopup = true; popupOptions.CanBeDismissedByTappingOutsideOfPopup = true; - + // Assert Assert.True(tapGestureRecognizer.Command?.CanExecute(null)); @@ -529,8 +529,8 @@ public void PopupPage_ShouldRespectLayoutOptions() Assert.Equal(LayoutOptions.Start, border.VerticalOptions); Assert.Equal(LayoutOptions.End, border.HorizontalOptions); } - - static TapGestureRecognizer GetTapOutsideGestureRecognizer(PopupPage popupPage) => + + static TapGestureRecognizer GetTapOutsideGestureRecognizer(PopupPage popupPage) => (TapGestureRecognizer)popupPage.Content.Children.OfType().Single().GestureRecognizers[0]; // Helper class for testing protected methods diff --git a/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupTests.cs b/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupTests.cs index 7d989d9898..09f6154cc6 100644 --- a/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupTests.cs +++ b/src/CommunityToolkit.Maui.UnitTests/Views/Popup/PopupTests.cs @@ -14,7 +14,7 @@ public void PopupBackgroundColor_DefaultValue_ShouldBeWhite() { Assert.Equal(PopupDefaults.BackgroundColor, Colors.White); } - + [Fact] public void CanBeDismissedByTappingOutsideOfPopup_DefaultValue_ShouldBeTrue() { @@ -158,7 +158,7 @@ public async Task PopupT_Close_ShouldNotThrowExceptionWhenCloseIsOverridden() await popup.CloseAsync(TestContext.Current.CancellationToken); await popup.CloseAsync("Hello", TestContext.Current.CancellationToken); } - + [Fact(Timeout = (int)TestDuration.Short)] public async Task ShowPopupAsync_TaskShouldCompleteWhenPopupCloseAsyncIsCalled() { diff --git a/src/CommunityToolkit.Maui/Primitives/Defaults/PopupDefaults.shared.cs b/src/CommunityToolkit.Maui/Primitives/Defaults/PopupDefaults.shared.cs index 31ebddac61..79a5e6dde3 100644 --- a/src/CommunityToolkit.Maui/Primitives/Defaults/PopupDefaults.shared.cs +++ b/src/CommunityToolkit.Maui/Primitives/Defaults/PopupDefaults.shared.cs @@ -31,7 +31,7 @@ static class PopupDefaults /// Default value for BackgroundColor /// public static Color BackgroundColor { get; } = Colors.White; - + /// /// Default value for /// diff --git a/src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs b/src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs index 5d35a7bf10..c971c92294 100644 --- a/src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs +++ b/src/CommunityToolkit.Maui/Views/Popup/Popup.shared.cs @@ -26,7 +26,7 @@ public partial class Popup : ContentView /// Bindable property to set the vertical position of the when displayed on screen /// public static new readonly BindableProperty VerticalOptionsProperty = View.VerticalOptionsProperty; - + /// /// Backing BindableProperty for the property. /// @@ -88,7 +88,7 @@ public Popup() get => base.VerticalOptions; set => base.VerticalOptions = value; } - + /// /> /// /// When true and the user taps outside the popup, it will dismiss. diff --git a/src/CommunityToolkit.Maui/Views/Popup/PopupPage.shared.cs b/src/CommunityToolkit.Maui/Views/Popup/PopupPage.shared.cs index 2fcea86ccd..ebf1a76e75 100644 --- a/src/CommunityToolkit.Maui/Views/Popup/PopupPage.shared.cs +++ b/src/CommunityToolkit.Maui/Views/Popup/PopupPage.shared.cs @@ -26,7 +26,6 @@ partial class PopupPage : ContentPage, IQueryAttributable readonly Popup popup; readonly IPopupOptions popupOptions; readonly Command tapOutsideOfPopupCommand; - readonly WeakEventManager popupClosedEventManager = new(); public PopupPage(View view, IPopupOptions popupOptions) : this(view as Popup ?? CreatePopupFromView(view), popupOptions) @@ -64,11 +63,7 @@ public PopupPage(Popup popup, IPopupOptions popupOptions) On().SetModalPresentationStyle(UIModalPresentationStyle.OverFullScreen); } - public event EventHandler PopupClosed - { - add => popupClosedEventManager.AddEventHandler(value); - remove => popupClosedEventManager.RemoveEventHandler(value); - } + public event EventHandler? PopupClosed; // Prevent Content from being set by external class // Casts `PopupPage.Content` to return typeof(PopupPageLayout) @@ -102,7 +97,7 @@ public async Task CloseAsync(PopupResult result, CancellationToken token = defau token.ThrowIfCancellationRequested(); await Navigation.PopModalAsync(false).WaitAsync(token); - popupClosedEventManager.HandleEvent(this, result, nameof(PopupClosed)); + PopupClosed?.Invoke(this, result); } protected override bool OnBackButtonPressed() @@ -164,7 +159,7 @@ void HandlePopupOptionsPropertyChanged(object? sender, PropertyChangedEventArgs tapOutsideOfPopupCommand.ChangeCanExecute(); } } - + void HandlePopupPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == Popup.CanBeDismissedByTappingOutsideOfPopupProperty.PropertyName)