From 9c05066718cc6d107d715fb27fc9663440502bc4 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 17 Oct 2022 19:11:12 +0200 Subject: [PATCH 1/7] Added integration tests for #8869. Some tests failing on Win32. --- samples/IntegrationTestApp/MainWindow.axaml | 6 ++ .../IntegrationTestApp/MainWindow.axaml.cs | 2 + .../IntegrationTestApp/ShowWindowTest.axaml | 2 +- .../WindowTests.cs | 80 +++++++++++++++++-- 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 3377979199..b200dade76 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -109,6 +109,12 @@ CenterScreen CenterOwner + + Normal + Minimized + Maximized + FullScreen + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index f72f83fcb8..3d929e8170 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -57,6 +57,7 @@ namespace IntegrationTestApp var sizeTextBox = this.GetControl("ShowWindowSize"); var modeComboBox = this.GetControl("ShowWindowMode"); var locationComboBox = this.GetControl("ShowWindowLocation"); + var stateComboBox = this.GetControl("ShowWindowState"); var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null; var owner = (Window)this.GetVisualRoot()!; @@ -83,6 +84,7 @@ namespace IntegrationTestApp } sizeTextBox.Text = string.Empty; + window.WindowState = (WindowState)stateComboBox.SelectedIndex; switch (modeComboBox.SelectedIndex) { diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml index 17c359df51..7dffa5494d 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml @@ -29,7 +29,7 @@ Normal Minimized Maximized - Fullscreen + FullScreen diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 9cce169744..7f2c1c49c7 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -57,7 +57,42 @@ namespace Avalonia.IntegrationTests.Appium } } } - + + [Theory] + [MemberData(nameof(WindowStateData))] + public void WindowState(Size? size, ShowWindowMode mode, WindowState state) + { + using var window = OpenWindow(size, mode, state: state); + + try + { + var info = GetWindowInfo(); + + Assert.Equal(state, info.WindowState); + + switch (state) + { + case Controls.WindowState.Normal: + Assert.True(info.FrameSize.Width * info.Scaling < info.ScreenRect.Size.Width); + Assert.True(info.FrameSize.Height * info.Scaling < info.ScreenRect.Size.Height); + break; + case Controls.WindowState.Maximized: + case Controls.WindowState.FullScreen: + Assert.True(info.FrameSize.Width * info.Scaling >= info.ScreenRect.Size.Width); + Assert.True(info.FrameSize.Height * info.Scaling >= info.ScreenRect.Size.Height); + break; + } + } + finally + { + try + { + _session.FindElementByAccessibilityId("WindowState").SendClick(); + _session.FindElementByName("Normal").SendClick(); + } catch { /* Ignore errors in cleanup */ } + } + } + [PlatformFact(TestPlatforms.Windows)] public void OnWindows_Docked_Windows_Retain_Size_Position_When_Restored() { @@ -100,7 +135,7 @@ namespace Avalonia.IntegrationTests.Appium [InlineData(ShowWindowMode.NonOwned)] [InlineData(ShowWindowMode.Owned)] [InlineData(ShowWindowMode.Modal)] - public void WindowState(ShowWindowMode mode) + public void ShowMode(ShowWindowMode mode) { using var window = OpenWindow(null, mode, WindowStartupLocation.Manual); var windowState = _session.FindElementByAccessibilityId("WindowState"); @@ -123,8 +158,8 @@ namespace Avalonia.IntegrationTests.Appium if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || mode == ShowWindowMode.NonOwned) { windowState.Click(); - _session.FindElementByName("Fullscreen").SendClick(); - Assert.Equal("Fullscreen", windowState.GetComboBoxValue()); + _session.FindElementByName("FullScreen").SendClick(); + Assert.Equal("FullScreen", windowState.GetComboBoxValue()); current = GetWindowInfo(); var clientSize = PixelSize.FromSize(current.ClientSize, current.Scaling); @@ -163,6 +198,27 @@ namespace Avalonia.IntegrationTests.Appium return data; } + public static TheoryData WindowStateData() + { + var sizes = new Size?[] { null, new Size(400, 300) }; + var data = new TheoryData(); + + foreach (var size in sizes) + { + foreach (var mode in Enum.GetValues()) + { + foreach (var state in Enum.GetValues()) + { + // Not sure how to handle testing minimized windows currently. + if (state != Controls.WindowState.Minimized) + data.Add(size, mode, state); + } + } + } + + return data; + } + private static void AssertCloseEnough(PixelPoint expected, PixelPoint actual) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -189,11 +245,16 @@ namespace Avalonia.IntegrationTests.Appium } } - private IDisposable OpenWindow(Size? size, ShowWindowMode mode, WindowStartupLocation location) + private IDisposable OpenWindow( + Size? size, + ShowWindowMode mode, + WindowStartupLocation location = WindowStartupLocation.Manual, + WindowState state = Controls.WindowState.Normal) { var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); + var stateComboBox = _session.FindElementByAccessibilityId("ShowWindowState"); var showButton = _session.FindElementByAccessibilityId("ShowWindow"); if (size.HasValue) @@ -205,6 +266,9 @@ namespace Avalonia.IntegrationTests.Appium locationComboBox.Click(); _session.FindElementByName(location.ToString()).SendClick(); + stateComboBox.Click(); + _session.FindElementByName(state.ToString()).SendClick(); + return showButton.OpenWindowWithClick(); } @@ -228,7 +292,8 @@ namespace Avalonia.IntegrationTests.Appium PixelPoint.Parse(_session.FindElementByAccessibilityId("Position").Text), ReadOwnerRect(), PixelRect.Parse(_session.FindElementByAccessibilityId("ScreenRect").Text), - double.Parse(_session.FindElementByAccessibilityId("Scaling").Text)); + double.Parse(_session.FindElementByAccessibilityId("Scaling").Text), + Enum.Parse(_session.FindElementByAccessibilityId("WindowState").Text)); } catch (OpenQA.Selenium.NoSuchElementException) when (retry++ < 3) { @@ -252,6 +317,7 @@ namespace Avalonia.IntegrationTests.Appium PixelPoint Position, PixelRect? OwnerRect, PixelRect ScreenRect, - double Scaling); + double Scaling, + WindowState WindowState); } } From 94db32430bd5ca02ba292b004c96ce9aa6ea85a2 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 17 Oct 2022 19:12:41 +0200 Subject: [PATCH 2/7] Correctly report FullScreen in WM_SIZE. If a `WM_SIZE` is received with `SizeCommand.Restored`, check the `_ifFullScreenActive` flag to determine if we're actually full-screen. --- src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs | 10 +++++++--- src/Windows/Avalonia.Win32/WindowImpl.cs | 3 +-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index f8785371d9..8c94077a0b 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -567,9 +567,13 @@ namespace Avalonia.Win32 Resized(clientSize / RenderScaling, _resizeReason); } - var windowState = size == SizeCommand.Maximized ? - WindowState.Maximized : - (size == SizeCommand.Minimized ? WindowState.Minimized : WindowState.Normal); + var windowState = size switch + { + SizeCommand.Maximized => WindowState.Maximized, + SizeCommand.Minimized => WindowState.Minimized, + _ when _isFullScreenActive => WindowState.FullScreen, + _ => WindowState.Normal, + }; if (windowState != _lastWindowState) { diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 0f243fcf9f..cc33b3c72f 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -900,11 +900,10 @@ namespace Avalonia.Win32 var window_rect = monitor_info.rcMonitor.ToPixelRect(); + _isFullScreenActive = true; SetWindowPos(_hwnd, IntPtr.Zero, window_rect.X, window_rect.Y, window_rect.Width, window_rect.Height, SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_FRAMECHANGED); - - _isFullScreenActive = true; } else { From 897c7746915fee6f67af08a12089a7f326bf2853 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 17 Oct 2022 22:29:39 +0200 Subject: [PATCH 3/7] Don't resize window when maximized/fullscreen. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index cc33b3c72f..7b6e073f97 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -267,6 +267,11 @@ namespace Avalonia.Win32 { get { + if (!IsWindowVisible(_hwnd)) + { + return _showWindowState; + } + if (_isFullScreenActive) { return WindowState.FullScreen; @@ -560,6 +565,9 @@ namespace Avalonia.Win32 public void Resize(Size value, PlatformResizeReason reason) { + if (WindowState is WindowState.Maximized or WindowState.FullScreen) + return; + int requestedClientWidth = (int)(value.Width * RenderScaling); int requestedClientHeight = (int)(value.Height * RenderScaling); From ccf1a35cc92bea525679e595caf4b2469080a1a8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 22 Oct 2022 00:32:56 +0200 Subject: [PATCH 4/7] Child/Modal windows cannot be fullscreen on macOS. --- .../Avalonia.IntegrationTests.Appium/WindowTests.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 7f2c1c49c7..b14c1f6cf3 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -210,8 +210,16 @@ namespace Avalonia.IntegrationTests.Appium foreach (var state in Enum.GetValues()) { // Not sure how to handle testing minimized windows currently. - if (state != Controls.WindowState.Minimized) - data.Add(size, mode, state); + if (state == Controls.WindowState.Minimized) + continue; + + // Child/Modal windows cannot be fullscreen on macOS. + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && + state == Controls.WindowState.FullScreen && + mode != ShowWindowMode.NonOwned) + continue; + + data.Add(size, mode, state); } } } From d413fbbb8e8b598c32f06e6288f3f311332adcf8 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 22 Oct 2022 00:36:25 +0200 Subject: [PATCH 5/7] Handle fullscreen windows better on mac. --- .../ElementExtensions.cs | 17 ++++++++++++----- .../WindowTests.cs | 5 +++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs index 4b361c6716..e7837a6971 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Reactive.Disposables; using System.Runtime.InteropServices; +using System.Threading; using OpenQA.Selenium; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Interactions; @@ -110,24 +111,30 @@ namespace Avalonia.IntegrationTests.Appium { var oldWindows = session.FindElements(By.XPath("/XCUIElementTypeApplication/XCUIElementTypeWindow")); var oldWindowTitles = oldWindows.ToDictionary(x => x.Text); - + element.Click(); + + // Wait for animations to run. + Thread.Sleep(1000); var newWindows = session.FindElements(By.XPath("/XCUIElementTypeApplication/XCUIElementTypeWindow")); var newWindowTitles = newWindows.ToDictionary(x => x.Text); var newWindowTitle = Assert.Single(newWindowTitles.Keys.Except(oldWindowTitles.Keys)); - var newWindow = (AppiumWebElement)newWindowTitles[newWindowTitle]; - + return Disposable.Create(() => { // TODO: We should be able to use Cmd+W here but Avalonia apps don't seem to have this shortcut // set up by default. - var (close, _, _) = newWindow.GetChromeButtons(); + var windows = session.FindElements(By.XPath("/XCUIElementTypeApplication/XCUIElementTypeWindow")); + var text = windows.Select(x => x.Text).ToList(); + var newWindow = session.FindElements(By.XPath("/XCUIElementTypeApplication/XCUIElementTypeWindow")) + .First(x => x.Text == newWindowTitle); + var (close, _, _) = ((AppiumWebElement)newWindow).GetChromeButtons(); close!.Click(); }); } } - + public static void SendClick(this AppiumWebElement element) { // The Click() method seems to correspond to accessibilityPerformPress on macOS but certain controls diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index b14c1f6cf3..9f542c75e2 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -89,6 +89,11 @@ namespace Avalonia.IntegrationTests.Appium { _session.FindElementByAccessibilityId("WindowState").SendClick(); _session.FindElementByName("Normal").SendClick(); + + // Wait for animations to run. + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + Thread.Sleep(1000); + } catch { /* Ignore errors in cleanup */ } } } From de852142a4250655f307e6b6ec21006fe83eb7db Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 22 Oct 2022 00:41:19 +0200 Subject: [PATCH 6/7] Speed up tests a little. --- .../WindowTests.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 9f542c75e2..b6ac75b78e 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -85,16 +85,22 @@ namespace Avalonia.IntegrationTests.Appium } finally { - try + if (state == Controls.WindowState.FullScreen) { - _session.FindElementByAccessibilityId("WindowState").SendClick(); - _session.FindElementByName("Normal").SendClick(); - - // Wait for animations to run. - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - Thread.Sleep(1000); + try + { + _session.FindElementByAccessibilityId("WindowState").SendClick(); + _session.FindElementByName("Normal").SendClick(); - } catch { /* Ignore errors in cleanup */ } + // Wait for animations to run. + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + Thread.Sleep(1000); + } + catch + { + /* Ignore errors in cleanup */ + } + } } } From 16afcecb993ac38b6eff75cc714a6ce9ab47c4d3 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 17 Nov 2022 15:56:25 +0100 Subject: [PATCH 7/7] Only resize if WindowState is Normal. --- src/Windows/Avalonia.Win32/WindowImpl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 4569b76d7d..42c576d01c 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -567,7 +567,7 @@ namespace Avalonia.Win32 public void Resize(Size value, PlatformResizeReason reason) { - if (WindowState is WindowState.Maximized or WindowState.FullScreen) + if (WindowState != WindowState.Normal) return; int requestedClientWidth = (int)(value.Width * RenderScaling);