From 1fd3de3a170c5ac18300c8c5534bbc17134f2748 Mon Sep 17 00:00:00 2001 From: SuthiYuvaraj <92777079+SuthiYuvaraj@users.noreply.github.com> Date: Tue, 19 Aug 2025 15:55:41 +0530 Subject: [PATCH 1/2] Update FlyoutPage.cs --- src/Controls/src/Core/FlyoutPage/FlyoutPage.cs | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/Controls/src/Core/FlyoutPage/FlyoutPage.cs b/src/Controls/src/Core/FlyoutPage/FlyoutPage.cs index 7ba6ce706a3b..9645e919a3b5 100644 --- a/src/Controls/src/Core/FlyoutPage/FlyoutPage.cs +++ b/src/Controls/src/Core/FlyoutPage/FlyoutPage.cs @@ -54,19 +54,7 @@ public Page Detail var previousDetail = _detail; - // Get the actual pages for navigation events (unwrap NavigationPages) - var destinationPage = - value is NavigationPage destinationNavPage ? destinationNavPage.CurrentPage : value; - var previousPage = previousDetail is NavigationPage previousNavPage - ? previousNavPage.CurrentPage - : previousDetail; - - // Send NavigatingFrom event to the previous detail (if any) - if (previousDetail is not null) - { - previousDetail.SendNavigatingFrom(new NavigatingFromEventArgs(destinationPage, - NavigationType.Replace)); - } + previousDetail?.SendNavigatingFrom(new NavigatingFromEventArgs(destinationPage: value, navigationType: NavigationType.Replace)); // Update the detail property OnPropertyChanging(); @@ -87,10 +75,10 @@ public Page Detail if (previousDetail is not null) { previousDetail.SendNavigatedFrom( - new NavigatedFromEventArgs(destinationPage, NavigationType.Replace)); + new NavigatedFromEventArgs(destinationPage: value, NavigationType.Replace)); } - _detail.SendNavigatedTo(new NavigatedToEventArgs(previousPage, NavigationType.Replace)); + _detail.SendNavigatedTo(new NavigatedToEventArgs(previousDetail, NavigationType.Replace)); } } From 5630faa18ff9fda6ebe11588dc2b1e264e577497 Mon Sep 17 00:00:00 2001 From: SuthiYuvaraj <92777079+SuthiYuvaraj@users.noreply.github.com> Date: Tue, 26 Aug 2025 18:21:30 +0530 Subject: [PATCH 2/2] Update FlyoutPageUnitTests.cs --- .../Core.UnitTests/FlyoutPageUnitTests.cs | 352 +++++++++++++++++- 1 file changed, 351 insertions(+), 1 deletion(-) diff --git a/src/Controls/tests/Core.UnitTests/FlyoutPageUnitTests.cs b/src/Controls/tests/Core.UnitTests/FlyoutPageUnitTests.cs index e9776de3aeb1..272a18fbcd27 100644 --- a/src/Controls/tests/Core.UnitTests/FlyoutPageUnitTests.cs +++ b/src/Controls/tests/Core.UnitTests/FlyoutPageUnitTests.cs @@ -443,6 +443,356 @@ public async Task VerifyToolbarButtonVisibilityWhenFlyoutReset(int depth) Assert.True(flyoutToolBar.IsVisible); } - } + [Fact] + public void FlyoutPageDetailNavigation_EventsWithContentPage() + { + // Test that navigation events are fired directly on the page when Detail is a ContentPage + var firstDetail = new NavigationObserverPage { Title = "First Detail" }; + var secondDetail = new NavigationObserverPage { Title = "Second Detail" }; + var flyoutPage = new FlyoutPage + { + Flyout = new ContentPage { Title = "Flyout" }, + Detail = firstDetail + }.AddToTestWindow(); + + flyoutPage.Detail = secondDetail; + + // Verify NavigatingFrom was called on the first detail directly + Assert.NotNull(firstDetail.NavigatingFromArgs); + Assert.Equal(secondDetail, firstDetail.NavigatingFromArgs.DestinationPage); + Assert.Equal(NavigationType.Replace, firstDetail.NavigatingFromArgs.NavigationType); + + // Verify NavigatedFrom was called on the first detail directly + Assert.NotNull(firstDetail.NavigatedFromArgs); + Assert.Equal(secondDetail, firstDetail.NavigatedFromArgs.DestinationPage); + Assert.Equal(NavigationType.Replace, firstDetail.NavigatedFromArgs.NavigationType); + + // Verify NavigatedTo was called on the second detail directly + Assert.NotNull(secondDetail.NavigatedToArgs); + Assert.Equal(firstDetail, secondDetail.NavigatedToArgs.PreviousPage); + Assert.Equal(NavigationType.Replace, secondDetail.NavigatedToArgs.NavigationType); + } + + [Fact] + public void FlyoutPageDetailNavigation_EventsWithNavigationPage() + { + // Test that navigation events are fired on NavigationPage directly, NOT on CurrentPage + var firstDetailContent = new NavigationObserverPage { Title = "First Detail Content" }; + var firstDetail = new TrackingNavigationPage(firstDetailContent) { Title = "First Detail Nav" }; + var secondDetailContent = new NavigationObserverPage { Title = "Second Detail Content" }; + var secondDetail = new TrackingNavigationPage(secondDetailContent) { Title = "Second Detail Nav" }; + var flyoutPage = new FlyoutPage + { + Flyout = new ContentPage { Title = "Flyout" }, + Detail = firstDetail + }.AddToTestWindow(); + + // Clear any initial navigation args on the content pages to focus on Detail replacement + firstDetailContent.ClearNavigationArgs(); + secondDetailContent.ClearNavigationArgs(); + firstDetail.ClearNavigationArgs(); + secondDetail.ClearNavigationArgs(); + + flyoutPage.Detail = secondDetail; + + // Verify that navigation events WERE fired on the NavigationPage containers + Assert.NotNull(firstDetail.NavigatingFromArgs); + Assert.Equal(secondDetail, firstDetail.NavigatingFromArgs.DestinationPage); + Assert.Equal(NavigationType.Replace, firstDetail.NavigatingFromArgs.NavigationType); + + Assert.NotNull(firstDetail.NavigatedFromArgs); + Assert.Equal(secondDetail, firstDetail.NavigatedFromArgs.DestinationPage); + Assert.Equal(NavigationType.Replace, firstDetail.NavigatedFromArgs.NavigationType); + + Assert.NotNull(secondDetail.NavigatedToArgs); + Assert.Equal(firstDetail, secondDetail.NavigatedToArgs.PreviousPage); + Assert.Equal(NavigationType.Replace, secondDetail.NavigatedToArgs.NavigationType); + } + + [Fact] + public void FlyoutPageDetailNavigation_MixedPageTypes() + { + // Test that navigation events work correctly when mixing ContentPage and NavigationPage + var firstDetail = new NavigationObserverPage { Title = "First Detail" }; + var secondDetailContent = new NavigationObserverPage { Title = "Second Detail Content" }; + var secondDetail = new TrackingNavigationPage(secondDetailContent) { Title = "Second Detail Nav" }; + var flyoutPage = new FlyoutPage + { + Flyout = new ContentPage { Title = "Flyout" }, + Detail = firstDetail + }.AddToTestWindow(); + + // Clear initial navigation args + secondDetailContent.ClearNavigationArgs(); + secondDetail.ClearNavigationArgs(); + + flyoutPage.Detail = secondDetail; + + // Verify NavigatingFrom was called on the ContentPage directly + Assert.NotNull(firstDetail.NavigatingFromArgs); + Assert.Equal(secondDetail, firstDetail.NavigatingFromArgs.DestinationPage); + Assert.Equal(NavigationType.Replace, firstDetail.NavigatingFromArgs.NavigationType); + + // Verify NavigatedFrom was called on the ContentPage directly + Assert.NotNull(firstDetail.NavigatedFromArgs); + Assert.Equal(secondDetail, firstDetail.NavigatedFromArgs.DestinationPage); + Assert.Equal(NavigationType.Replace, firstDetail.NavigatedFromArgs.NavigationType); + + // Verify NavigatedTo was called on the NavigationPage container + Assert.NotNull(secondDetail.NavigatedToArgs); + Assert.Equal(firstDetail, secondDetail.NavigatedToArgs.PreviousPage); + Assert.Equal(NavigationType.Replace, secondDetail.NavigatedToArgs.NavigationType); + } + + [Fact] + public void FlyoutPageDetailNavigation_NavigationPageToContentPage() + { + // Test navigation from NavigationPage to ContentPage + var firstDetailContent = new NavigationObserverPage { Title = "First Detail Content" }; + var firstDetail = new TrackingNavigationPage(firstDetailContent) { Title = "First Detail Nav" }; + var secondDetail = new NavigationObserverPage { Title = "Second Detail" }; + var flyoutPage = new FlyoutPage + { + Flyout = new ContentPage { Title = "Flyout" }, + Detail = firstDetail + }.AddToTestWindow(); + + // Clear any initial navigation args + firstDetailContent.ClearNavigationArgs(); + firstDetail.ClearNavigationArgs(); + secondDetail.ClearNavigationArgs(); + + flyoutPage.Detail = secondDetail; + + // Verify NavigatingFrom was called on the NavigationPage container + Assert.NotNull(firstDetail.NavigatingFromArgs); + Assert.Equal(secondDetail, firstDetail.NavigatingFromArgs.DestinationPage); + Assert.Equal(NavigationType.Replace, firstDetail.NavigatingFromArgs.NavigationType); + + // Verify NavigatedFrom was called on the NavigationPage container + Assert.NotNull(firstDetail.NavigatedFromArgs); + Assert.Equal(secondDetail, firstDetail.NavigatedFromArgs.DestinationPage); + Assert.Equal(NavigationType.Replace, firstDetail.NavigatedFromArgs.NavigationType); + + // Verify NavigatedTo was called on the ContentPage with NavigationPage as previous page + Assert.NotNull(secondDetail.NavigatedToArgs); + Assert.Equal(firstDetail, secondDetail.NavigatedToArgs.PreviousPage); + Assert.Equal(NavigationType.Replace, secondDetail.NavigatedToArgs.NavigationType); + } + + [Fact] + public void FlyoutPageFlyoutNavigation_EventsWithContentPage() + { + // Test that navigation events are fired directly on Flyout pages when they are ContentPages + var firstFlyout = new NavigationObserverPage { Title = "First Flyout" }; + var secondFlyout = new NavigationObserverPage { Title = "Second Flyout" }; + var flyoutPage = new FlyoutPage + { + Flyout = firstFlyout, + Detail = new ContentPage { Title = "Detail" } + }.AddToTestWindow(); + + flyoutPage.Flyout = secondFlyout; + + // Verify NavigatingFrom was called on the first flyout directly + Assert.NotNull(firstFlyout.NavigatingFromArgs); + Assert.Equal(secondFlyout, firstFlyout.NavigatingFromArgs.DestinationPage); + Assert.Equal(NavigationType.Replace, firstFlyout.NavigatingFromArgs.NavigationType); + + // Verify NavigatedFrom was called on the first flyout directly + Assert.NotNull(firstFlyout.NavigatedFromArgs); + Assert.Equal(secondFlyout, firstFlyout.NavigatedFromArgs.DestinationPage); + Assert.Equal(NavigationType.Replace, firstFlyout.NavigatedFromArgs.NavigationType); + + // Verify NavigatedTo was called on the second flyout directly + Assert.NotNull(secondFlyout.NavigatedToArgs); + Assert.Equal(firstFlyout, secondFlyout.NavigatedToArgs.PreviousPage); + Assert.Equal(NavigationType.Replace, secondFlyout.NavigatedToArgs.NavigationType); + } + + [Fact] + public void FlyoutPageFlyoutNavigation_EventsWithNavigationPage() + { + // Test that navigation events are fired on NavigationPage directly for Flyout, NOT on CurrentPage + var firstFlyoutContent = new NavigationObserverPage { Title = "First Flyout Content" }; + var firstFlyout = new TrackingNavigationPage(firstFlyoutContent) { Title = "First Flyout Nav" }; + var secondFlyoutContent = new NavigationObserverPage { Title = "Second Flyout Content" }; + var secondFlyout = new TrackingNavigationPage(secondFlyoutContent) { Title = "Second Flyout Nav" }; + var flyoutPage = new FlyoutPage + { + Flyout = firstFlyout, + Detail = new ContentPage { Title = "Detail" } + }.AddToTestWindow(); + + // Clear any initial navigation args on the content pages + firstFlyoutContent.ClearNavigationArgs(); + secondFlyoutContent.ClearNavigationArgs(); + firstFlyout.ClearNavigationArgs(); + secondFlyout.ClearNavigationArgs(); + + flyoutPage.Flyout = secondFlyout; + + // Verify that navigation events WERE fired on the NavigationPage containers + Assert.NotNull(firstFlyout.NavigatingFromArgs); + Assert.Equal(secondFlyout, firstFlyout.NavigatingFromArgs.DestinationPage); + Assert.Equal(NavigationType.Replace, firstFlyout.NavigatingFromArgs.NavigationType); + + Assert.NotNull(firstFlyout.NavigatedFromArgs); + Assert.Equal(secondFlyout, firstFlyout.NavigatedFromArgs.DestinationPage); + Assert.Equal(NavigationType.Replace, firstFlyout.NavigatedFromArgs.NavigationType); + + Assert.NotNull(secondFlyout.NavigatedToArgs); + Assert.Equal(firstFlyout, secondFlyout.NavigatedToArgs.PreviousPage); + Assert.Equal(NavigationType.Replace, secondFlyout.NavigatedToArgs.NavigationType); + } + + [Fact] + public void FlyoutPageDetailNavigation_CorrectNavigationType() + { + // Test that all navigation events use NavigationType.Replace + var firstDetail = new NavigationObserverPage { Title = "First Detail" }; + var secondDetail = new NavigationObserverPage { Title = "Second Detail" }; + var flyoutPage = new FlyoutPage + { + Flyout = new ContentPage { Title = "Flyout" }, + Detail = firstDetail + }.AddToTestWindow(); + + flyoutPage.Detail = secondDetail; + + Assert.Equal(NavigationType.Replace, firstDetail.NavigatingFromArgs.NavigationType); + Assert.Equal(NavigationType.Replace, firstDetail.NavigatedFromArgs.NavigationType); + Assert.Equal(NavigationType.Replace, secondDetail.NavigatedToArgs.NavigationType); + } + + [Fact] + public void FlyoutPageFlyoutNavigation_CorrectNavigationType() + { + // Test that all Flyout navigation events use NavigationType.Replace + var firstFlyout = new NavigationObserverPage { Title = "First Flyout" }; + var secondFlyout = new NavigationObserverPage { Title = "Second Flyout" }; + var flyoutPage = new FlyoutPage + { + Flyout = firstFlyout, + Detail = new ContentPage { Title = "Detail" } + }.AddToTestWindow(); + + flyoutPage.Flyout = secondFlyout; + + Assert.Equal(NavigationType.Replace, firstFlyout.NavigatingFromArgs.NavigationType); + Assert.Equal(NavigationType.Replace, firstFlyout.NavigatedFromArgs.NavigationType); + Assert.Equal(NavigationType.Replace, secondFlyout.NavigatedToArgs.NavigationType); + } + + [Fact] + public void FlyoutPageDetailNavigation_NullPreviousDetailHandled() + { + // Test that navigation events are handled correctly when there's no previous Detail + var detail = new NavigationObserverPage { Title = "Detail" }; + var flyoutPage = new FlyoutPage + { + Flyout = new ContentPage { Title = "Flyout" } + }; + + // Set detail first, which should trigger NavigatedTo with null previous page + flyoutPage.Detail = detail; + + // Then add to window after both properties are set + var window = flyoutPage.AddToTestWindow(); + + // Verify NavigatedTo was called with null previous page + Assert.NotNull(detail.NavigatedToArgs); + Assert.Null(detail.NavigatedToArgs.PreviousPage); + Assert.Equal(NavigationType.Replace, detail.NavigatedToArgs.NavigationType); + } + + [Fact] + public void FlyoutPageFlyoutNavigation_NullPreviousFlyoutHandled() + { + // Test that navigation events are handled correctly when there's no previous Flyout + var flyout = new NavigationObserverPage { Title = "Flyout" }; + var flyoutPage = new FlyoutPage + { + Detail = new ContentPage { Title = "Detail" } + }; + + // Set flyout first, which should trigger NavigatedTo with null previous page + flyoutPage.Flyout = flyout; + + // Then add to window after both properties are set + var window = flyoutPage.AddToTestWindow(); + + // Verify NavigatedTo was called with null previous page + Assert.NotNull(flyout.NavigatedToArgs); + Assert.Null(flyout.NavigatedToArgs.PreviousPage); + Assert.Equal(NavigationType.Replace, flyout.NavigatedToArgs.NavigationType); + } + + public class NavigationObserverPage : ContentPage + { + public NavigatedFromEventArgs NavigatedFromArgs { get; private set; } + public NavigatingFromEventArgs NavigatingFromArgs { get; private set; } + public NavigatedToEventArgs NavigatedToArgs { get; private set; } + + public void ClearNavigationArgs() + { + NavigatedFromArgs = null; + NavigatingFromArgs = null; + NavigatedToArgs = null; + } + + protected override void OnNavigatedFrom(NavigatedFromEventArgs args) + { + base.OnNavigatedFrom(args); + NavigatedFromArgs = args; + } + + protected override void OnNavigatingFrom(NavigatingFromEventArgs args) + { + base.OnNavigatingFrom(args); + NavigatingFromArgs = args; + } + + protected override void OnNavigatedTo(NavigatedToEventArgs args) + { + base.OnNavigatedTo(args); + NavigatedToArgs = args; + } + } + + public class TrackingNavigationPage : NavigationPage + { + public NavigatedFromEventArgs NavigatedFromArgs { get; private set; } + public NavigatingFromEventArgs NavigatingFromArgs { get; private set; } + public NavigatedToEventArgs NavigatedToArgs { get; private set; } + + public TrackingNavigationPage(Page root) : base(root) { } + + public void ClearNavigationArgs() + { + NavigatedFromArgs = null; + NavigatingFromArgs = null; + NavigatedToArgs = null; + } + + protected override void OnNavigatedFrom(NavigatedFromEventArgs args) + { + base.OnNavigatedFrom(args); + NavigatedFromArgs = args; + } + + protected override void OnNavigatingFrom(NavigatingFromEventArgs args) + { + base.OnNavigatingFrom(args); + NavigatingFromArgs = args; + } + + protected override void OnNavigatedTo(NavigatedToEventArgs args) + { + base.OnNavigatedTo(args); + NavigatedToArgs = args; + } + } + } }