diff --git a/src/Controls/tests/Core.UnitTests/SafeAreaOrientationTests.cs b/src/Controls/tests/Core.UnitTests/SafeAreaOrientationTests.cs new file mode 100644 index 000000000000..6a5028ccaa78 --- /dev/null +++ b/src/Controls/tests/Core.UnitTests/SafeAreaOrientationTests.cs @@ -0,0 +1,159 @@ +using Microsoft.Maui.Devices; +using Microsoft.Maui.Graphics; +using Xunit; + +namespace Microsoft.Maui.Controls.Core.UnitTests; + +public class SafeAreaOrientationTests : BaseTestFixture +{ + [Fact] + public void SafeAreaEdgesCanBeSetViaVisualStateManager() + { + DeviceDisplay.SetCurrent(new MockDeviceDisplay( + new(100, 200, 2, DisplayOrientation.Portrait, DisplayRotation.Rotation0))); + + var page = new ContentPage(); + + VisualStateManager.SetVisualStateGroups(page, new VisualStateGroupList + { + new VisualStateGroup + { + Name = "OrientationStates", + States = + { + new VisualState + { + Name = "Portrait", + StateTriggers = { new OrientationStateTrigger { Orientation = DisplayOrientation.Portrait } }, + Setters = { new Setter { Property = ContentPage.SafeAreaEdgesProperty, Value = SafeAreaEdges.All } } + }, + new VisualState + { + Name = "Landscape", + StateTriggers = { new OrientationStateTrigger { Orientation = DisplayOrientation.Landscape } }, + Setters = { new Setter { Property = ContentPage.SafeAreaEdgesProperty, Value = new SafeAreaEdges(SafeAreaRegions.All, SafeAreaRegions.None) } } + } + } + } + }); + + var window = new Window(page); + + // Initially in portrait, should have All edges + Assert.Equal(SafeAreaEdges.All, page.SafeAreaEdges); + } + + [Fact] + public void SafeAreaEdgesChangesWhenOrientationChanges() + { + var mockDeviceDisplay = new MockDeviceDisplay( + new(100, 200, 2, DisplayOrientation.Portrait, DisplayRotation.Rotation0)); + DeviceDisplay.SetCurrent(mockDeviceDisplay); + + var page = new ContentPage(); + + VisualStateManager.SetVisualStateGroups(page, new VisualStateGroupList + { + new VisualStateGroup + { + Name = "OrientationStates", + States = + { + new VisualState + { + Name = "Portrait", + StateTriggers = { new OrientationStateTrigger { Orientation = DisplayOrientation.Portrait } }, + Setters = { new Setter { Property = ContentPage.SafeAreaEdgesProperty, Value = SafeAreaEdges.All } } + }, + new VisualState + { + Name = "Landscape", + StateTriggers = { new OrientationStateTrigger { Orientation = DisplayOrientation.Landscape } }, + Setters = { new Setter { Property = ContentPage.SafeAreaEdgesProperty, Value = new SafeAreaEdges(SafeAreaRegions.All, SafeAreaRegions.None) } } + } + } + } + }); + + var window = new Window(page); + + // Initially in portrait + Assert.Equal(SafeAreaEdges.All, page.SafeAreaEdges); + + // Change to landscape + mockDeviceDisplay.SetMainDisplayOrientation(DisplayOrientation.Landscape); + + // Should now use different safe area edges (horizontal All, vertical None) + var expected = new SafeAreaEdges(SafeAreaRegions.All, SafeAreaRegions.None); + Assert.Equal(expected, page.SafeAreaEdges); + + window.Page = null; // Cleanup + } + + [Fact] + public void SafeAreaEdgesCanUseContainerRegion() + { + DeviceDisplay.SetCurrent(new MockDeviceDisplay( + new(100, 200, 2, DisplayOrientation.Portrait, DisplayRotation.Rotation0))); + + var page = new ContentPage(); + + VisualStateManager.SetVisualStateGroups(page, new VisualStateGroupList + { + new VisualStateGroup + { + Name = "OrientationStates", + States = + { + new VisualState + { + Name = "Portrait", + StateTriggers = { new OrientationStateTrigger { Orientation = DisplayOrientation.Portrait } }, + Setters = { new Setter { Property = ContentPage.SafeAreaEdgesProperty, Value = SafeAreaEdges.Container } } + } + } + } + }); + + var window = new Window(page); + + // Should use Container region + Assert.Equal(SafeAreaEdges.Container, page.SafeAreaEdges); + + window.Page = null; // Cleanup + } + + [Fact] + public void SafeAreaEdgesWorksWithDifferentPageTypes() + { + DeviceDisplay.SetCurrent(new MockDeviceDisplay( + new(100, 200, 2, DisplayOrientation.Portrait, DisplayRotation.Rotation0))); + + var contentPage = new ContentPage(); + var tabbedPage = new TabbedPage(); + + VisualStateManager.SetVisualStateGroups(contentPage, new VisualStateGroupList + { + new VisualStateGroup + { + Name = "OrientationStates", + States = + { + new VisualState + { + Name = "Portrait", + StateTriggers = { new OrientationStateTrigger { Orientation = DisplayOrientation.Portrait } }, + Setters = { new Setter { Property = ContentPage.SafeAreaEdgesProperty, Value = SafeAreaEdges.All } } + } + } + } + }); + + var window = new Window(contentPage); + + // ContentPage should respect VSM + Assert.Equal(SafeAreaEdges.All, contentPage.SafeAreaEdges); + + window.Page = null; // Cleanup + } +} diff --git a/src/Controls/tests/TestCases.HostApp/Issues/PageSafeAreaOrientationVSM.xaml b/src/Controls/tests/TestCases.HostApp/Issues/PageSafeAreaOrientationVSM.xaml new file mode 100644 index 000000000000..aa427dea76d1 --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/PageSafeAreaOrientationVSM.xaml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Controls/tests/TestCases.HostApp/Issues/PageSafeAreaOrientationVSM.xaml.cs b/src/Controls/tests/TestCases.HostApp/Issues/PageSafeAreaOrientationVSM.xaml.cs new file mode 100644 index 000000000000..a6d1f26b990a --- /dev/null +++ b/src/Controls/tests/TestCases.HostApp/Issues/PageSafeAreaOrientationVSM.xaml.cs @@ -0,0 +1,48 @@ +namespace Maui.Controls.Sample.Issues; + +[Issue(IssueTracker.None, 0, "Page SafeAreaEdges with OrientationStateTrigger VSM Test", PlatformAffected.All)] +public partial class PageSafeAreaOrientationVSM : ContentPage +{ + public PageSafeAreaOrientationVSM() + { + InitializeComponent(); + + // Monitor size changes for orientation detection + this.SizeChanged += OnSizeChanged; + + // Update info when page appears + this.Appearing += OnPageAppearing; + } + + private void OnPageAppearing(object sender, EventArgs e) + { + // Delay to allow layout to complete + Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(500), UpdateInfo); + } + + private void OnSizeChanged(object sender, EventArgs e) + { + // Update info when size changes (indicating orientation change) + Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(100), UpdateInfo); + } + + private void UpdateInfo() + { + try + { + // Determine orientation based on actual page dimensions + var isLandscape = this.Width > this.Height; + var orientationText = isLandscape ? "Landscape" : "Portrait"; + + OrientationLabel.Text = $"Orientation: {orientationText}"; + SafeAreaEdgesLabel.Text = $"SafeAreaEdges: {this.SafeAreaEdges}"; + PageDimensionsLabel.Text = $"Page Dimensions: {this.Width:F1} x {this.Height:F1}"; + } + catch (Exception ex) + { + OrientationLabel.Text = $"Orientation: Error - {ex.Message}"; + SafeAreaEdgesLabel.Text = "SafeAreaEdges: Error"; + PageDimensionsLabel.Text = "Page Dimensions: Error"; + } + } +} diff --git a/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/PageSafeAreaOrientationVSM.cs b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/PageSafeAreaOrientationVSM.cs new file mode 100644 index 000000000000..dbad457f95a2 --- /dev/null +++ b/src/Controls/tests/TestCases.Shared.Tests/Tests/Issues/PageSafeAreaOrientationVSM.cs @@ -0,0 +1,96 @@ +#if IOS || ANDROID +using NUnit.Framework; +using UITest.Appium; +using UITest.Core; + +namespace Microsoft.Maui.TestCases.Tests.Issues; + +public class PageSafeAreaOrientationVSM : _IssuesUITest +{ + public override string Issue => "Page SafeAreaEdges with OrientationStateTrigger VSM Test"; + + public PageSafeAreaOrientationVSM(TestDevice device) : base(device) + { + } + + [Test] + [Category(UITestCategories.SafeAreaEdges)] + public void PageSafeAreaEdgesUpdatesWithOrientation() + { + // 1. Wait for initial state to load + var orientationLabel = App.WaitForElement("OrientationLabel"); + var safeAreaEdgesLabel = App.WaitForElement("SafeAreaEdgesLabel"); + + // 2. Verify initial portrait state + var initialOrientation = orientationLabel.GetText(); + var initialSafeAreaEdges = safeAreaEdgesLabel.GetText(); + + Assert.That(initialOrientation, Does.Contain("Portrait"), + "Should start in portrait orientation"); + Assert.That(initialSafeAreaEdges, Does.Contain("Container"), + "SafeAreaEdges should be Container in portrait"); + + // 3. Change to landscape + App.SetOrientationLandscape(); + Thread.Sleep(2000); // Wait for orientation change and VSM to trigger + + // 4. Verify landscape state + var landscapeOrientation = App.WaitForElement("OrientationLabel").GetText(); + var landscapeSafeAreaEdges = App.WaitForElement("SafeAreaEdgesLabel").GetText(); + + Assert.That(landscapeOrientation, Does.Contain("Landscape"), + "Should now be in landscape orientation"); + Assert.That(landscapeSafeAreaEdges, Does.Contain("Container"), + "SafeAreaEdges should be Container in landscape"); + + // 5. Verify visual markers are still visible + var topLeftMarker = App.WaitForElement("TopLeftMarker"); + var topRightMarker = App.WaitForElement("TopRightMarker"); + var centerMarker = App.WaitForElement("CenterMarker"); + + Assert.That(topLeftMarker, Is.Not.Null, "Top left marker should be visible"); + Assert.That(topRightMarker, Is.Not.Null, "Top right marker should be visible"); + Assert.That(centerMarker, Is.Not.Null, "Center marker should be visible"); + + // 6. Change back to portrait + App.SetOrientationPortrait(); + Thread.Sleep(2000); // Wait for orientation change and VSM to trigger + + // 7. Verify back to portrait state + var finalOrientation = App.WaitForElement("OrientationLabel").GetText(); + var finalSafeAreaEdges = App.WaitForElement("SafeAreaEdgesLabel").GetText(); + + Assert.That(finalOrientation, Does.Contain("Portrait"), + "Should be back in portrait orientation"); + Assert.That(finalSafeAreaEdges, Does.Contain("Container"), + "SafeAreaEdges should still be Container in portrait"); + } + + [Test] + [Category(UITestCategories.SafeAreaEdges)] + public void PageVSMMultipleOrientationChanges() + { + // Test that the VSM handles multiple orientation changes correctly + var safeAreaEdgesLabel = App.WaitForElement("SafeAreaEdgesLabel"); + + for (int i = 0; i < 3; i++) + { + // Change to landscape + App.SetOrientationLandscape(); + Thread.Sleep(1500); + + var landscapeSafeArea = safeAreaEdgesLabel.GetText(); + Assert.That(landscapeSafeArea, Does.Contain("Container"), + $"SafeAreaEdges should be Container in landscape (iteration {i + 1})"); + + // Change back to portrait + App.SetOrientationPortrait(); + Thread.Sleep(1500); + + var portraitSafeArea = safeAreaEdgesLabel.GetText(); + Assert.That(portraitSafeArea, Does.Contain("Container"), + $"SafeAreaEdges should be Container in portrait (iteration {i + 1})"); + } + } +} +#endif diff --git a/src/Templates/src/templates/maui-mobile/Resources/Styles/Styles.xaml b/src/Templates/src/templates/maui-mobile/Resources/Styles/Styles.xaml index 2a9a28bfca2a..c94b8cb72e39 100644 --- a/src/Templates/src/templates/maui-mobile/Resources/Styles/Styles.xaml +++ b/src/Templates/src/templates/maui-mobile/Resources/Styles/Styles.xaml @@ -443,6 +443,28 @@ + + + + + + + + + + + + + + + + + + + + + +