From f4cc30d4a10149d4a9d9f88578ee35b3ecfa0b0a Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sun, 29 May 2022 22:28:28 +0200 Subject: [PATCH 01/25] Add failing test for Window.Width/Height. --- .../WindowTests.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs index 63ccf74c2b..a8c9b68d12 100644 --- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs +++ b/tests/Avalonia.Controls.UnitTests/WindowTests.cs @@ -695,6 +695,31 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void Width_Height_Should_Not_Be_NaN_After_Show_With_SizeToContent_Manual() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var child = new Canvas + { + Width = 400, + Height = 800, + }; + + var target = new Window() + { + SizeToContent = SizeToContent.Manual, + Content = child + }; + + Show(target); + + // Values come from MockWindowingPlatform defaults. + Assert.Equal(800, target.Width); + Assert.Equal(600, target.Height); + } + } + [Fact] public void Width_Height_Should_Not_Be_NaN_After_Show_With_SizeToContent_WidthAndHeight() { @@ -712,6 +737,8 @@ namespace Avalonia.Controls.UnitTests Content = child }; + target.GetObservable(Window.WidthProperty).Subscribe(x => { }); + Show(target); Assert.Equal(400, target.Width); From 138be304a0d2c86c663846bfa7adfb1537058fd1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 30 May 2022 11:53:34 +0200 Subject: [PATCH 02/25] Ensure Window.Width/Height is initialized. - Run resize logic if `Width`/`Height` are still NaN (i.e. not set up) - Always call base class `WindowBase.HandleResize` - In `WindowBase.HandleResize` don't run a layout pass if client size unchanged Fixes `Width_Height_Should_Not_Be_NaN_After_Show_With_SizeToContent_Manual`. --- src/Avalonia.Controls/Window.cs | 40 ++++++++++++++--------------- src/Avalonia.Controls/WindowBase.cs | 12 ++++++--- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index a4f4534b88..9b49e866b8 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -991,28 +991,28 @@ namespace Avalonia.Controls /// protected sealed override void HandleResized(Size clientSize, PlatformResizeReason reason) { - if (ClientSize == clientSize) - return; - - var sizeToContent = SizeToContent; - - // If auto-sizing is enabled, and the resize came from a user resize (or the reason was - // unspecified) then turn off auto-resizing for any window dimension that is not equal - // to the requested size. - if (sizeToContent != SizeToContent.Manual && - CanResize && - reason == PlatformResizeReason.Unspecified || - reason == PlatformResizeReason.User) + if (ClientSize != clientSize || double.IsNaN(Width) || double.IsNaN(Height)) { - if (clientSize.Width != ClientSize.Width) - sizeToContent &= ~SizeToContent.Width; - if (clientSize.Height != ClientSize.Height) - sizeToContent &= ~SizeToContent.Height; - SizeToContent = sizeToContent; - } + var sizeToContent = SizeToContent; + + // If auto-sizing is enabled, and the resize came from a user resize (or the reason was + // unspecified) then turn off auto-resizing for any window dimension that is not equal + // to the requested size. + if (sizeToContent != SizeToContent.Manual && + CanResize && + reason == PlatformResizeReason.Unspecified || + reason == PlatformResizeReason.User) + { + if (clientSize.Width != ClientSize.Width) + sizeToContent &= ~SizeToContent.Width; + if (clientSize.Height != ClientSize.Height) + sizeToContent &= ~SizeToContent.Height; + SizeToContent = sizeToContent; + } - Width = clientSize.Width; - Height = clientSize.Height; + Width = clientSize.Width; + Height = clientSize.Height; + } base.HandleResized(clientSize, reason); } diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index 12ba143c8a..d5e54a5c08 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -217,10 +217,16 @@ namespace Avalonia.Controls /// The reason for the resize. protected override void HandleResized(Size clientSize, PlatformResizeReason reason) { - ClientSize = clientSize; FrameSize = PlatformImpl?.FrameSize; - LayoutManager.ExecuteLayoutPass(); - Renderer?.Resized(clientSize); + + if (ClientSize != clientSize) + { + ClientSize = clientSize; + LayoutManager.ExecuteLayoutPass(); + Renderer?.Resized(clientSize); + } + + System.Diagnostics.Debug.WriteLine($"HandleResized: ClientSize {ClientSize} | FrameSize {FrameSize}"); } /// From 67e6c41abcbaed905bba22446ee54b995a29bed1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 30 May 2022 11:54:34 +0200 Subject: [PATCH 03/25] Use FrameSize for window startup location. ...if available. --- src/Avalonia.Controls/Window.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 9b49e866b8..92f74530e2 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -871,10 +871,10 @@ namespace Avalonia.Controls var scaling = owner?.DesktopScaling ?? PlatformImpl?.DesktopScaling ?? 1; - // TODO: We really need non-client size here. - var rect = new PixelRect( - PixelPoint.Origin, - PixelSize.FromSize(ClientSize, scaling)); + // Use frame size, falling back to client size if the platform can't give it to us. + var rect = FrameSize.HasValue ? + new PixelRect(PixelSize.FromSize(FrameSize.Value, scaling)) : + new PixelRect(PixelSize.FromSize(ClientSize, scaling)); if (startupLocation == WindowStartupLocation.CenterScreen) { From 4c8240b7bf349ca5bc7f74344ba45af73b77f57c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 30 May 2022 11:57:13 +0200 Subject: [PATCH 04/25] Added integration tests for WindowStartupLocation. --- samples/IntegrationTestApp/MainWindow.axaml | 17 ++ .../IntegrationTestApp/MainWindow.axaml.cs | 38 ++++ .../IntegrationTestApp/ShowWindowTest.axaml | 26 +++ .../ShowWindowTest.axaml.cs | 40 +++++ .../Avalonia.IntegrationTests.Appium.csproj | 4 + .../WindowTests.cs | 164 ++++++++++++++++++ 6 files changed, 289 insertions(+) create mode 100644 samples/IntegrationTestApp/ShowWindowTest.axaml create mode 100644 samples/IntegrationTestApp/ShowWindowTest.axaml.cs create mode 100644 tests/Avalonia.IntegrationTests.Appium/WindowTests.cs diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 19ac68b15b..5151d80932 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -94,6 +94,23 @@ + + + + + + NonOwned + Owned + Modal + + + Manual + CenterScreen + CenterOwner + + + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 9a612aa94d..580548a433 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -5,6 +5,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; +using Avalonia.VisualTree; namespace IntegrationTestApp { @@ -46,6 +47,41 @@ namespace IntegrationTestApp } } + private void ShowWindow() + { + var sizeTextBox = this.GetControl("ShowWindowSize"); + var modeComboBox = this.GetControl("ShowWindowMode"); + var locationComboBox = this.GetControl("ShowWindowLocation"); + var size = !string.IsNullOrWhiteSpace(sizeTextBox.Text) ? Size.Parse(sizeTextBox.Text) : (Size?)null; + var owner = (Window)this.GetVisualRoot()!; + + var window = new ShowWindowTest + { + WindowStartupLocation = (WindowStartupLocation)locationComboBox.SelectedIndex, + }; + + if (size.HasValue) + { + window.Width = size.Value.Width; + window.Height = size.Value.Height; + } + + sizeTextBox.Text = string.Empty; + + switch (modeComboBox.SelectedIndex) + { + case 0: + window.Show(); + break; + case 1: + window.Show(owner); + break; + case 2: + window.ShowDialog(owner); + break; + } + } + private void MenuClicked(object? sender, RoutedEventArgs e) { var clickedMenuItemTextBlock = this.FindControl("ClickedMenuItem"); @@ -64,6 +100,8 @@ namespace IntegrationTestApp this.FindControl("BasicListBox").SelectedIndex = -1; if (source?.Name == "MenuClickedMenuItemReset") this.FindControl("ClickedMenuItem").Text = "None"; + if (source?.Name == "ShowWindow") + ShowWindow(); } } } diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml new file mode 100644 index 0000000000..e87ae0f32c --- /dev/null +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs new file mode 100644 index 0000000000..9b55864caa --- /dev/null +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs @@ -0,0 +1,40 @@ +using System; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using Avalonia.Rendering; + +namespace IntegrationTestApp +{ + public class ShowWindowTest : Window + { + public ShowWindowTest() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + protected override void OnOpened(EventArgs e) + { + base.OnOpened(e); + this.GetControl("ClientSize").Text = $"{Width}, {Height}"; + this.GetControl("FrameSize").Text = $"{FrameSize}"; + this.GetControl("Position").Text = $"{Position}"; + this.GetControl("ScreenRect").Text = $"{Screens.ScreenFromVisual(this)?.WorkingArea}"; + this.GetControl("Scaling").Text = $"{((IRenderRoot)this).RenderScaling}"; + + if (Owner is not null) + { + var ownerRect = this.GetControl("OwnerRect"); + var owner = (Window)Owner; + ownerRect.Text = $"{owner.Position}, {owner.FrameSize}"; + } + } + + private void CloseWindow_Click(object sender, RoutedEventArgs e) => Close(); + } +} diff --git a/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj index 095f0e63e0..03d9332051 100644 --- a/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj +++ b/tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj @@ -5,6 +5,10 @@ enable + + + + diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs new file mode 100644 index 0000000000..771faa1925 --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -0,0 +1,164 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; +using Avalonia.Controls; +using OpenQA.Selenium.Appium; +using Xunit; +using Xunit.Sdk; + +namespace Avalonia.IntegrationTests.Appium +{ + [Collection("Default")] + public class WindowTests + { + private readonly AppiumDriver _session; + + public WindowTests(TestAppFixture fixture) + { + _session = fixture.Session; + + var tabs = _session.FindElementByAccessibilityId("MainTabs"); + var tab = tabs.FindElementByName("Window"); + tab.Click(); + } + + [Theory] + [MemberData(nameof(StartupLocationData))] + public void StartupLocation(string? size, ShowWindowMode mode, WindowStartupLocation location) + { + var mainWindowHandle = GetCurrentWindowHandleHack(); + + try + { + var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); + var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); + var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); + var showButton = _session.FindElementByAccessibilityId("ShowWindow"); + + if (size is not null) + sizeTextBox.SendKeys(size); + + modeComboBox.Click(); + _session.FindElementByName(mode.ToString()).SendClick(); + + locationComboBox.Click(); + _session.FindElementByName(location.ToString()).SendClick(); + + showButton.Click(); + SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle); + + var clientSize = Size.Parse(_session.FindElementByAccessibilityId("ClientSize").Text); + var frameSize = Size.Parse(_session.FindElementByAccessibilityId("FrameSize").Text); + var position = PixelPoint.Parse(_session.FindElementByAccessibilityId("Position").Text); + var screenRect = PixelRect.Parse(_session.FindElementByAccessibilityId("ScreenRect").Text); + var scaling = double.Parse(_session.FindElementByAccessibilityId("Scaling").Text); + + Assert.True(frameSize.Width >= clientSize.Width, "Expected frame width >= client width."); + Assert.True(frameSize.Height > clientSize.Height, "Expected frame height > client height."); + + var frameRect = new PixelRect(position, PixelSize.FromSize(frameSize, scaling)); + + switch (location) + { + case WindowStartupLocation.CenterScreen: + { + var expected = screenRect.CenterRect(frameRect); + AssertCloseEnough(expected.Position, frameRect.Position); + break; + } + } + } + finally + { + try + { + var closeButton = _session.FindElementByAccessibilityId("CloseWindow"); + closeButton.Click(); + SwitchToMainWindowHack(mainWindowHandle); + } + catch { } + } + } + + public static TheoryData StartupLocationData() + { + var sizes = new[] { null, "400,300" }; + var data = new TheoryData(); + + foreach (var size in sizes) + { + foreach (var mode in Enum.GetValues()) + { + foreach (var location in Enum.GetValues()) + { + if (!(location == WindowStartupLocation.CenterOwner && mode == ShowWindowMode.NonOwned)) + { + data.Add(size, mode, location); + } + } + } + } + + return data; + } + + private string? GetCurrentWindowHandleHack() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // HACK: WinAppDriver only seems to switch to a newly opened window if the window has an owner, + // otherwise the session remains targeting the previous window. Return the handle for the + // current window so we know which window to switch to when another is opened. + return _session.WindowHandles.Single(); + } + + return null; + } + + private void SwitchToNewWindowHack(string? oldWindowHandle) + { + if (oldWindowHandle is not null) + { + var newWindowHandle = _session.WindowHandles.FirstOrDefault(x => x != oldWindowHandle); + + // HACK: Looks like WinAppDriver only adds window handles for non-owned windows, but luckily + // non-owned windows is where we're having the problem, so if we find a window handle that + // isn't the main window handle then switch to it. + if (newWindowHandle is not null) + _session.SwitchTo().Window(newWindowHandle); + } + } + + private void SwitchToMainWindowHack(string? mainWindowHandle) + { + if (mainWindowHandle is not null) + _session.SwitchTo().Window(mainWindowHandle); + } + + private static void AssertCloseEnough(PixelPoint expected, PixelPoint actual) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // On win32, accurate frame information cannot be obtained until a window is shown but + // WindowStartupLocation needs to be calculated before the window is shown, meaning that + // the position of a centered window can be off by a bit. From initial testing, looks + // like this shouldn't be more than 10 pixels. + if (Math.Abs(expected.X - actual.X) > 10) + throw new EqualException(expected, actual); + if (Math.Abs(expected.Y - actual.Y) > 10) + throw new EqualException(expected, actual); + } + else + { + Assert.Equal(expected, actual); + } + } + + public enum ShowWindowMode + { + NonOwned, + Owned, + Modal + } + } +} From a2d83e8fae5a559bca83d87db7f5a30d901618ed Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 31 May 2022 13:11:07 +0100 Subject: [PATCH 05/25] [OSX] programatically implement child window relationship --- native/Avalonia.Native/src/OSX/AvnView.mm | 5 ++ native/Avalonia.Native/src/OSX/AvnWindow.mm | 38 +++++------ .../Avalonia.Native/src/OSX/WindowBaseImpl.h | 2 + .../Avalonia.Native/src/OSX/WindowBaseImpl.mm | 7 +- native/Avalonia.Native/src/OSX/WindowImpl.h | 8 ++- native/Avalonia.Native/src/OSX/WindowImpl.mm | 66 +++++++++++++------ .../Avalonia.Native/src/OSX/WindowProtocol.h | 1 - 7 files changed, 81 insertions(+), 46 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnView.mm b/native/Avalonia.Native/src/OSX/AvnView.mm index 5436ad22f3..bbb4d59adb 100644 --- a/native/Avalonia.Native/src/OSX/AvnView.mm +++ b/native/Avalonia.Native/src/OSX/AvnView.mm @@ -300,6 +300,11 @@ - (void)mouseDown:(NSEvent *)event { + if(_parent != nullptr) + { + _parent->BringToFront(); + } + _isLeftPressed = true; _lastMouseDownEvent = event; [self mouseEvent:event withType:LeftButtonDown]; diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index 590dc5e7ac..1445227cf5 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -183,6 +183,11 @@ return self; } +- (void)mouseDown:(NSEvent *)event +{ + _parent->BringToFront(); +} + - (BOOL)windowShouldClose:(NSWindow *)sender { auto window = dynamic_cast(_parent.getRaw()); @@ -209,7 +214,14 @@ { ComPtr parent = _parent; _parent = NULL; - [self restoreParentWindow]; + + auto window = dynamic_cast(parent.getRaw()); + + if(window != nullptr) + { + window->SetParent(nullptr); + } + parent->BaseEvents->Closed(); [parent->View onClosed]; } @@ -220,17 +232,11 @@ if(_canBecomeKeyWindow) { // If the window has a child window being shown as a dialog then don't allow it to become the key window. - for(NSWindow* uch in [self childWindows]) + auto parent = dynamic_cast(_parent.getRaw()); + + if(parent != nullptr) { - if (![uch conformsToProtocol:@protocol(AvnWindowProtocol)]) - { - continue; - } - - id ch = (id ) uch; - - if(ch.isDialog) - return false; + return parent->CanBecomeKeyWindow(); } return true; @@ -273,16 +279,6 @@ [super becomeKeyWindow]; } --(void) restoreParentWindow; -{ - auto parent = [self parentWindow]; - - if(parent != nil) - { - [parent removeChildWindow:self]; - } -} - - (void)windowDidMiniaturize:(NSNotification *)notification { auto parent = dynamic_cast(_parent.operator->()); diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index 83850e780c..62c0e2069d 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h @@ -99,6 +99,8 @@ BEGIN_INTERFACE_MAP() virtual bool IsDialog(); id GetWindowProtocol (); + + virtual void BringToFront (); protected: virtual NSWindowStyleMask GetStyle(); diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 121679b942..77f0f47934 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -143,8 +143,6 @@ HRESULT WindowBaseImpl::Hide() { @autoreleasepool { if (Window != nullptr) { [Window orderOut:Window]; - - [GetWindowProtocol() restoreParentWindow]; } return S_OK; @@ -610,6 +608,11 @@ id WindowBaseImpl::GetWindowProtocol() { return (id ) Window; } +void WindowBaseImpl::BringToFront() +{ + // do nothing. +} + extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl) { @autoreleasepool diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.h b/native/Avalonia.Native/src/OSX/WindowImpl.h index 35627685a2..76d5cbf6ea 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowImpl.h @@ -8,6 +8,7 @@ #import "WindowBaseImpl.h" #include "IWindowStateChanged.h" +#include class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged { @@ -22,7 +23,8 @@ private: bool _transitioningWindowState; bool _isClientAreaExtended; bool _isDialog; - WindowImpl* _lastParent; + WindowImpl* _parent; + std::list _children; AvnExtendClientAreaChromeHints _extendClientHints; FORWARD_IUNKNOWN() @@ -91,6 +93,10 @@ BEGIN_INTERFACE_MAP() virtual bool IsDialog() override; virtual void OnInitialiseNSWindow() override; + + virtual void BringToFront () override; + + bool CanBecomeKeyWindow (); protected: virtual NSWindowStyleMask GetStyle() override; diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index ad804eb280..5333cb23c8 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -10,6 +10,7 @@ #include "WindowProtocol.h" WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBaseImpl(events, gl) { + _children = std::list(); _isClientAreaExtended = false; _extendClientHints = AvnDefaultChrome; _fullScreenActive = false; @@ -20,7 +21,7 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase _lastWindowState = Normal; _actualWindowState = Normal; _lastTitle = @""; - _lastParent = nullptr; + _parent = nullptr; WindowEvents = events; } @@ -63,9 +64,9 @@ void WindowImpl::OnInitialiseNSWindow(){ SetExtendClientArea(true); } - if(_lastParent != nullptr) + if(_parent != nullptr) { - SetParent(_lastParent); + SetParent(_parent); } } @@ -96,33 +97,56 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) { START_COM_CALL; @autoreleasepool { - if (parent == nullptr) - return E_POINTER; + if(_parent != nullptr) + { + _parent->_children.remove(this); + } auto cparent = dynamic_cast(parent); - if (cparent == nullptr) - return E_INVALIDARG; - - _lastParent = cparent; + _parent = cparent; - if(Window != nullptr){ - // If one tries to show a child window with a minimized parent window, then the parent window will be - // restored but macOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive - // state. Detect this and explicitly restore the parent window ourselves to avoid this situation. - if (cparent->WindowState() == Minimized) - cparent->SetWindowState(Normal); - - [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; - [cparent->Window addChildWindow:Window ordered:NSWindowAbove]; - - UpdateStyle(); + if(_parent != nullptr && Window != nullptr){ + // If one tries to show a child window with a minimized parent window, then the parent window will be + // restored but macOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive + // state. Detect this and explicitly restore the parent window ourselves to avoid this situation. + if (cparent->WindowState() == Minimized) + cparent->SetWindowState(Normal); + + [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; + + cparent->_children.push_back(this); + + UpdateStyle(); } return S_OK; } } +void WindowImpl::BringToFront() +{ + Activate(); + + for(auto iterator = _children.begin(); iterator != _children.end(); iterator++) + { + (*iterator)->BringToFront(); + } +} + +bool WindowImpl::CanBecomeKeyWindow() +{ + for(auto iterator = _children.begin(); iterator != _children.end(); iterator++) + { + if((*iterator)->IsDialog()) + { + return false; + } + } + + return true; +} + void WindowImpl::StartStateTransition() { _transitioningWindowState = true; } @@ -534,7 +558,7 @@ bool WindowImpl::IsDialog() { } NSWindowStyleMask WindowImpl::GetStyle() { - unsigned long s = this->_isDialog ? NSWindowStyleMaskDocModalWindow : NSWindowStyleMaskBorderless; + unsigned long s = NSWindowStyleMaskBorderless; switch (_decorations) { case SystemDecorationsNone: diff --git a/native/Avalonia.Native/src/OSX/WindowProtocol.h b/native/Avalonia.Native/src/OSX/WindowProtocol.h index 0e5c5869e7..cb5f86bdb9 100644 --- a/native/Avalonia.Native/src/OSX/WindowProtocol.h +++ b/native/Avalonia.Native/src/OSX/WindowProtocol.h @@ -11,7 +11,6 @@ @protocol AvnWindowProtocol -(void) pollModalSession: (NSModalSession _Nonnull) session; --(void) restoreParentWindow; -(bool) shouldTryToHandleEvents; -(void) setEnabled: (bool) enable; -(void) showAppMenuOnly; From dc1b6a669b8a90bc71344ea6b6df28e2b489e7a0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 31 May 2022 13:50:01 +0100 Subject: [PATCH 06/25] [osx] make bringtofront work correctly for owned and modal windows. --- native/Avalonia.Native/src/OSX/AvnView.mm | 5 ----- native/Avalonia.Native/src/OSX/AvnWindow.mm | 9 +++------ native/Avalonia.Native/src/OSX/WindowImpl.mm | 9 ++++++++- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/AvnView.mm b/native/Avalonia.Native/src/OSX/AvnView.mm index bbb4d59adb..5436ad22f3 100644 --- a/native/Avalonia.Native/src/OSX/AvnView.mm +++ b/native/Avalonia.Native/src/OSX/AvnView.mm @@ -300,11 +300,6 @@ - (void)mouseDown:(NSEvent *)event { - if(_parent != nullptr) - { - _parent->BringToFront(); - } - _isLeftPressed = true; _lastMouseDownEvent = event; [self mouseEvent:event withType:LeftButtonDown]; diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index 1445227cf5..60fdb26121 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -183,11 +183,6 @@ return self; } -- (void)mouseDown:(NSEvent *)event -{ - _parent->BringToFront(); -} - - (BOOL)windowShouldClose:(NSWindow *)sender { auto window = dynamic_cast(_parent.getRaw()); @@ -435,8 +430,10 @@ _parent->BaseEvents->RawMouseEvent(NonClientLeftButtonDown, static_cast([event timestamp] * 1000), AvnInputModifiersNone, point, delta); } + + _parent->BringToFront(); } - break; + break; case NSEventTypeMouseEntered: { diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 5333cb23c8..8330f4ed86 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -126,7 +126,14 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) { void WindowImpl::BringToFront() { - Activate(); + if(IsDialog()) + { + Activate(); + } + else + { + [Window orderFront:nullptr]; + } for(auto iterator = _children.begin(); iterator != _children.end(); iterator++) { From f7daa81fbe8d684c13d0d10c8fb2727734e2e480 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 31 May 2022 14:10:33 +0100 Subject: [PATCH 07/25] dont create nspanel / nswindow at show. --- native/Avalonia.Native/src/OSX/WindowBaseImpl.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index 77f0f47934..d105f4cf38 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -36,8 +36,10 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl) lastMaxSize = NSSize { CGFLOAT_MAX, CGFLOAT_MAX}; lastMinSize = NSSize { 0, 0 }; - Window = nullptr; lastMenu = nullptr; + + CreateNSWindow(false); + InitialiseNSWindow(); } HRESULT WindowBaseImpl::ObtainNSViewHandle(void **ret) { @@ -88,7 +90,6 @@ HRESULT WindowBaseImpl::Show(bool activate, bool isDialog) { START_COM_CALL; @autoreleasepool { - CreateNSWindow(isDialog); InitialiseNSWindow(); if(hasPosition) @@ -585,6 +586,7 @@ void WindowBaseImpl::InitialiseNSWindow() { [Window setOpaque:false]; + [Window setHasShadow:true]; [Window invalidateShadow]; if (lastMenu != nullptr) { From 041fdb6bc9bcb06711c7715692e2d5771ad6a2eb Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 31 May 2022 14:11:19 +0100 Subject: [PATCH 08/25] call bring to front when window is made key. --- native/Avalonia.Native/src/OSX/AvnWindow.mm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm index 60fdb26121..52ee48317c 100644 --- a/native/Avalonia.Native/src/OSX/AvnWindow.mm +++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm @@ -274,6 +274,11 @@ [super becomeKeyWindow]; } +- (void)windowDidBecomeKey:(NSNotification *)notification +{ + _parent->BringToFront(); +} + - (void)windowDidMiniaturize:(NSNotification *)notification { auto parent = dynamic_cast(_parent.operator->()); From 2ea49defb60cf0a8412dabde876a2d8c27e1d8c5 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 31 May 2022 14:28:44 +0100 Subject: [PATCH 09/25] [osx] easily support using nspanel from windowbaseimpl. --- native/Avalonia.Native/src/OSX/WindowBaseImpl.h | 2 +- native/Avalonia.Native/src/OSX/WindowBaseImpl.mm | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h index 62c0e2069d..4220811fc7 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.h +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.h @@ -26,7 +26,7 @@ BEGIN_INTERFACE_MAP() virtual ~WindowBaseImpl(); - WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl); + WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, bool usePanel = false); virtual HRESULT ObtainNSWindowHandle(void **ret) override; diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm index d105f4cf38..e88c7f208c 100644 --- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm @@ -21,7 +21,7 @@ WindowBaseImpl::~WindowBaseImpl() { Window = nullptr; } -WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl) { +WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl, bool usePanel) { _shown = false; _inResize = false; BaseEvents = events; @@ -38,7 +38,7 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl) lastMenu = nullptr; - CreateNSWindow(false); + CreateNSWindow(usePanel); InitialiseNSWindow(); } From 76abbc8fbef53c161763e0387d46ace4b22819e0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 31 May 2022 16:34:22 +0100 Subject: [PATCH 10/25] ensure frameSize is refreshed when window is show. --- src/Avalonia.Controls/TopLevel.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index 57fb82485c..f2e8cdb1cf 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -484,7 +484,11 @@ namespace Avalonia.Controls /// Raises the event. /// /// The event args. - protected virtual void OnOpened(EventArgs e) => Opened?.Invoke(this, e); + protected virtual void OnOpened(EventArgs e) + { + FrameSize = PlatformImpl?.FrameSize; + Opened?.Invoke(this, e); + } /// /// Raises the event. From 67b6811ca8d18ed7e4063cb712577a260a0de232 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 31 May 2022 16:34:33 +0100 Subject: [PATCH 11/25] add comment to show why test isnt working. --- samples/IntegrationTestApp/ShowWindowTest.axaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs index 9b55864caa..f5f87c5c1c 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs @@ -25,7 +25,7 @@ namespace IntegrationTestApp this.GetControl("FrameSize").Text = $"{FrameSize}"; this.GetControl("Position").Text = $"{Position}"; this.GetControl("ScreenRect").Text = $"{Screens.ScreenFromVisual(this)?.WorkingArea}"; - this.GetControl("Scaling").Text = $"{((IRenderRoot)this).RenderScaling}"; + this.GetControl("Scaling").Text = $"{((IRenderRoot)this).RenderScaling}"; // TODO Use DesktopScaling from WindowImpl. if (Owner is not null) { From cef238d1950e7ec195f4d8f201181bde29b7f207 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 1 Jun 2022 10:10:43 +0100 Subject: [PATCH 12/25] dispatch bring to front parent when removing a child window. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 8330f4ed86..7776b2912d 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -100,6 +100,11 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) { if(_parent != nullptr) { _parent->_children.remove(this); + auto parent = _parent; + + dispatch_async(dispatch_get_main_queue(), ^{ + parent->BringToFront(); + }); } auto cparent = dynamic_cast(parent); From f017acae5685fe8b7c5f9fdf9cbd4308dea08c2e Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 1 Jun 2022 10:15:54 +0100 Subject: [PATCH 13/25] dialogs should not be minimizable. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index 7776b2912d..8520e3e470 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -590,7 +590,7 @@ NSWindowStyleMask WindowImpl::GetStyle() { break; } - if ([Window parentWindow] == nullptr) { + if (!IsDialog()) { s |= NSWindowStyleMaskMiniaturizable; } From 0d6e3a55f016bf521263735eac066d26bab37d47 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 1 Jun 2022 17:07:02 +0100 Subject: [PATCH 14/25] add test to show that osx chrome buttons are disabled when modal dialog is opened. --- .../WindowTests.cs | 59 ++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 771faa1925..a82c46b927 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -22,6 +22,63 @@ namespace Avalonia.IntegrationTests.Appium tab.Click(); } + [PlatformFact(SkipOnWindows = true)] + public void OSX_Parent_Window_Has_Disabled_ChromeButtons_When_Modal_Dialog_Shown() + { + var mainWindowHandle = GetCurrentWindowHandleHack(); + + try + { + var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); + var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); + var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); + var showButton = _session.FindElementByAccessibilityId("ShowWindow"); + + var closeButton = + _session.FindElementByXPath( + "/XCUIElementTypeApplication/XCUIElementTypeWindow/XCUIElementTypeButton[1]"); + + var zoomButton = + _session.FindElementByXPath( + "/XCUIElementTypeApplication/XCUIElementTypeWindow/XCUIElementTypeButton[2]"); + + var miniturizeButton = + _session.FindElementByXPath( + "/XCUIElementTypeApplication/XCUIElementTypeWindow/XCUIElementTypeButton[3]"); + + + Assert.True(closeButton.Enabled); + Assert.True(zoomButton.Enabled); + Assert.True(miniturizeButton.Enabled); + + sizeTextBox.SendKeys("400, 400"); + + modeComboBox.Click(); + _session.FindElementByName(ShowWindowMode.Modal.ToString()).SendClick(); + + locationComboBox.Click(); + _session.FindElementByName(WindowStartupLocation.CenterOwner.ToString()).SendClick(); + + showButton.Click(); + + SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle); + + Assert.False(closeButton.Enabled); + Assert.False(zoomButton.Enabled); + Assert.False(miniturizeButton.Enabled); + } + finally + { + try + { + var closeButton = _session.FindElementByAccessibilityId("CloseWindow"); + closeButton.Click(); + SwitchToMainWindowHack(mainWindowHandle); + } + catch { } + } + } + [Theory] [MemberData(nameof(StartupLocationData))] public void StartupLocation(string? size, ShowWindowMode mode, WindowStartupLocation location) @@ -52,7 +109,7 @@ namespace Avalonia.IntegrationTests.Appium var position = PixelPoint.Parse(_session.FindElementByAccessibilityId("Position").Text); var screenRect = PixelRect.Parse(_session.FindElementByAccessibilityId("ScreenRect").Text); var scaling = double.Parse(_session.FindElementByAccessibilityId("Scaling").Text); - + Assert.True(frameSize.Width >= clientSize.Width, "Expected frame width >= client width."); Assert.True(frameSize.Height > clientSize.Height, "Expected frame height > client height."); From b7397a7cdf710fe17d49ee3993196fefd734d74b Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 1 Jun 2022 17:56:36 +0100 Subject: [PATCH 15/25] add a11y identifiers for windows. --- samples/IntegrationTestApp/MainWindow.axaml | 1 + samples/IntegrationTestApp/ShowWindowTest.axaml | 1 + 2 files changed, 2 insertions(+) diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 5151d80932..82348691e9 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="IntegrationTestApp.MainWindow" + Name="MainWindow" Title="IntegrationTestApp"> diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml index e87ae0f32c..2525bfc866 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml @@ -1,6 +1,7 @@ From a7713dcda9d0b40d9f1dbcbd21ce2d4cea94f9ef Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 1 Jun 2022 17:56:52 +0100 Subject: [PATCH 16/25] add a unit test for child window order osx. --- .../WindowTests.cs | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index a82c46b927..1f5ac052ec 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -2,7 +2,9 @@ using System.Linq; using System.Runtime.InteropServices; using Avalonia.Controls; +using OpenQA.Selenium; using OpenQA.Selenium.Appium; +using SeleniumExtras.PageObjects; using Xunit; using Xunit.Sdk; @@ -21,6 +23,79 @@ namespace Avalonia.IntegrationTests.Appium var tab = tabs.FindElementByName("Window"); tab.Click(); } + + [PlatformFact(SkipOnWindows = true)] + public void OSX_WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent() + { + var mainWindowHandle = GetCurrentWindowHandleHack(); + + try + { + var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); + var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); + var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); + var showButton = _session.FindElementByAccessibilityId("ShowWindow"); + + var mainWindow = + _session.FindElementByAccessibilityId("MainWindow"); + + + + sizeTextBox.SendKeys("200, 100"); + + modeComboBox.Click(); + _session.FindElementByName(ShowWindowMode.Modal.ToString()).SendClick(); + + locationComboBox.Click(); + _session.FindElementByName(WindowStartupLocation.CenterOwner.ToString()).SendClick(); + + showButton.Click(); + + var secondaryWindow = _session.FindElementByAccessibilityId("SecondaryWindow"); + + mainWindow.Click(); + + var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); + + int i = 0; + int mainWindowIndex = 0; + int secondaryWindowIndex = 0; + + foreach (var window in windows) + { + i++; + + var child = window.FindElementByXPath("XCUIElementTypeWindow"); + + switch (child.GetAttribute("identifier")) + { + case "MainWindow": + mainWindowIndex = i; + break; + + case "SecondaryWindow": + secondaryWindowIndex = i; + break; + } + + } + + Assert.Equal(1, secondaryWindowIndex); + Assert.Equal(2, mainWindowIndex); + + SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle); + } + finally + { + try + { + var closeButton = _session.FindElementByAccessibilityId("CloseWindow"); + closeButton.Click(); + SwitchToMainWindowHack(mainWindowHandle); + } + catch { } + } + } [PlatformFact(SkipOnWindows = true)] public void OSX_Parent_Window_Has_Disabled_ChromeButtons_When_Modal_Dialog_Shown() From a70dadea18bd0ff1fb9e0b1a6fa657659eb608a4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 1 Jun 2022 18:17:15 +0100 Subject: [PATCH 17/25] simplify test. --- .../WindowTests.cs | 42 +++++++------------ 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 1f5ac052ec..8d6be4d199 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using Avalonia.Controls; @@ -39,8 +40,6 @@ namespace Avalonia.IntegrationTests.Appium var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); - - sizeTextBox.SendKeys("200, 100"); modeComboBox.Click(); @@ -51,37 +50,15 @@ namespace Avalonia.IntegrationTests.Appium showButton.Click(); - var secondaryWindow = _session.FindElementByAccessibilityId("SecondaryWindow"); - mainWindow.Click(); var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - int i = 0; - int mainWindowIndex = 0; - int secondaryWindowIndex = 0; + int mainWindowIndex = windows.GetWindowOrder("MainWindow"); + int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); - foreach (var window in windows) - { - i++; - - var child = window.FindElementByXPath("XCUIElementTypeWindow"); - - switch (child.GetAttribute("identifier")) - { - case "MainWindow": - mainWindowIndex = i; - break; - - case "SecondaryWindow": - secondaryWindowIndex = i; - break; - } - - } - - Assert.Equal(1, secondaryWindowIndex); - Assert.Equal(2, mainWindowIndex); + Assert.Equal(0, secondaryWindowIndex); + Assert.Equal(1, mainWindowIndex); SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle); } @@ -293,4 +270,13 @@ namespace Avalonia.IntegrationTests.Appium Modal } } + + static class Extensions + { + public static int GetWindowOrder(this IReadOnlyCollection elements, string identifier) + { + return elements.TakeWhile(x => + x.FindElementByXPath("XCUIElementTypeWindow")?.GetAttribute("identifier") != identifier).Count(); + } + } } From 1917def95927cb95d55a1d08ea14943cd5e03849 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 2 Jun 2022 14:15:02 +0100 Subject: [PATCH 18/25] full set of window tests for osx. --- .../ShowWindowTest.axaml.cs | 2 +- .../WindowTests.cs | 226 +++++++++++++----- 2 files changed, 165 insertions(+), 63 deletions(-) diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs index f5f87c5c1c..3f45f1c5ad 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs @@ -25,7 +25,7 @@ namespace IntegrationTestApp this.GetControl("FrameSize").Text = $"{FrameSize}"; this.GetControl("Position").Text = $"{Position}"; this.GetControl("ScreenRect").Text = $"{Screens.ScreenFromVisual(this)?.WorkingArea}"; - this.GetControl("Scaling").Text = $"{((IRenderRoot)this).RenderScaling}"; // TODO Use DesktopScaling from WindowImpl. + this.GetControl("Scaling").Text = $"{PlatformImpl?.DesktopScaling}"; if (Owner is not null) { diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 8d6be4d199..b027c17ff3 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reactive.Disposables; using System.Runtime.InteropServices; using Avalonia.Controls; using OpenQA.Selenium; @@ -24,53 +25,117 @@ namespace Avalonia.IntegrationTests.Appium var tab = tabs.FindElementByName("Window"); tab.Click(); } + + private IDisposable OpenWindow(ShowWindowMode mode, WindowStartupLocation location, int width = 200, int height = 100) + { + var mainWindow = GetCurrentWindowHandleHack(); + var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); + var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); + var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); + var showButton = _session.FindElementByAccessibilityId("ShowWindow"); + + sizeTextBox.SendKeys($"{width}, {height}"); + + modeComboBox.Click(); + _session.FindElementByName(mode.ToString()).SendClick(); + + locationComboBox.Click(); + _session.FindElementByName(location.ToString()).SendClick(); + + showButton.Click(); + + return Disposable.Create(() => + { + try + { + SwitchToNewWindowHack(mainWindow); + var closeButton = _session.FindElementByAccessibilityId("CloseWindow"); + closeButton.SendClick(); + } + catch { } + }); + } [PlatformFact(SkipOnWindows = true)] public void OSX_WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent() { var mainWindowHandle = GetCurrentWindowHandleHack(); + var mainWindow = + _session.FindElementByAccessibilityId("MainWindow"); + try { - var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); - var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); - var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); - var showButton = _session.FindElementByAccessibilityId("ShowWindow"); + using (OpenWindow(ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + { + mainWindow.Click(); - var mainWindow = - _session.FindElementByAccessibilityId("MainWindow"); - - sizeTextBox.SendKeys("200, 100"); + var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - modeComboBox.Click(); - _session.FindElementByName(ShowWindowMode.Modal.ToString()).SendClick(); + int mainWindowIndex = windows.GetWindowOrder("MainWindow"); + int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); - locationComboBox.Click(); - _session.FindElementByName(WindowStartupLocation.CenterOwner.ToString()).SendClick(); + Assert.Equal(0, secondaryWindowIndex); + Assert.Equal(1, mainWindowIndex); + } + } + finally + { + SwitchToMainWindowHack(mainWindowHandle); + } + } + + [PlatformFact(SkipOnWindows = true)] + public void OSX_WindowOrder_Owned_Dialog_Stays_InFront_Of_Parent() + { + var mainWindowHandle = GetCurrentWindowHandleHack(); + + var mainWindow = + _session.FindElementByAccessibilityId("MainWindow"); - showButton.Click(); + try + { + using (OpenWindow(ShowWindowMode.Owned, WindowStartupLocation.CenterOwner)) + { + mainWindow.Click(); + var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); + + int mainWindowIndex = windows.GetWindowOrder("MainWindow"); + int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); + + Assert.Equal(0, secondaryWindowIndex); + Assert.Equal(1, mainWindowIndex); + } + } + finally + { + SwitchToMainWindowHack(mainWindowHandle); + } + } + + [PlatformFact(SkipOnWindows = true)] + public void OSX_WindowOrder_NonOwned_Window_Does_Not_Stay_InFront_Of_Parent() + { + var mainWindow = + _session.FindElementByAccessibilityId("MainWindow"); + + using (OpenWindow(ShowWindowMode.NonOwned, WindowStartupLocation.CenterOwner, 1400)) + { mainWindow.Click(); + var secondaryWindow = + _session.FindElementByAccessibilityId("SecondaryWindow"); + var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); int mainWindowIndex = windows.GetWindowOrder("MainWindow"); int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); - - Assert.Equal(0, secondaryWindowIndex); - Assert.Equal(1, mainWindowIndex); - SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle); - } - finally - { - try - { - var closeButton = _session.FindElementByAccessibilityId("CloseWindow"); - closeButton.Click(); - SwitchToMainWindowHack(mainWindowHandle); - } - catch { } + Assert.Equal(1, secondaryWindowIndex); + Assert.Equal(0, mainWindowIndex); + + secondaryWindow.SendClick(); } } @@ -81,53 +146,54 @@ namespace Avalonia.IntegrationTests.Appium try { - var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); - var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); - var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); - var showButton = _session.FindElementByAccessibilityId("ShowWindow"); + var window = _session.FindWindowOuter("MainWindow"); - var closeButton = - _session.FindElementByXPath( - "/XCUIElementTypeApplication/XCUIElementTypeWindow/XCUIElementTypeButton[1]"); - - var zoomButton = - _session.FindElementByXPath( - "/XCUIElementTypeApplication/XCUIElementTypeWindow/XCUIElementTypeButton[2]"); - - var miniturizeButton = - _session.FindElementByXPath( - "/XCUIElementTypeApplication/XCUIElementTypeWindow/XCUIElementTypeButton[3]"); - + var (closeButton, zoomButton, miniturizeButton) + = window.GetChromeButtons(); Assert.True(closeButton.Enabled); Assert.True(zoomButton.Enabled); Assert.True(miniturizeButton.Enabled); - - sizeTextBox.SendKeys("400, 400"); - modeComboBox.Click(); - _session.FindElementByName(ShowWindowMode.Modal.ToString()).SendClick(); - - locationComboBox.Click(); - _session.FindElementByName(WindowStartupLocation.CenterOwner.ToString()).SendClick(); + using (OpenWindow(ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + { + SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle); - showButton.Click(); - - SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle); - - Assert.False(closeButton.Enabled); - Assert.False(zoomButton.Enabled); - Assert.False(miniturizeButton.Enabled); + Assert.False(closeButton.Enabled); + Assert.False(zoomButton.Enabled); + Assert.False(miniturizeButton.Enabled); + } } finally { - try + SwitchToMainWindowHack(mainWindowHandle); + } + } + + [PlatformFact(SkipOnWindows = true)] + public void OSX_Minimize_Button_Disabled_Modal_Dialog() + { + var mainWindowHandle = GetCurrentWindowHandleHack(); + + try + { + using (OpenWindow(ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) { - var closeButton = _session.FindElementByAccessibilityId("CloseWindow"); - closeButton.Click(); - SwitchToMainWindowHack(mainWindowHandle); + var secondaryWindow = _session.FindWindowOuter("SecondaryWindow"); + + var (closeButton, zoomButton, miniturizeButton) + = secondaryWindow.GetChromeButtons(); + + Assert.True(closeButton.Enabled); + Assert.True(zoomButton.Enabled); + Assert.False(miniturizeButton.Enabled); + + SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle); } - catch { } + } + finally + { + SwitchToMainWindowHack(mainWindowHandle); } } @@ -257,6 +323,13 @@ namespace Avalonia.IntegrationTests.Appium if (Math.Abs(expected.Y - actual.Y) > 10) throw new EqualException(expected, actual); } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + if (Math.Abs(expected.X - actual.X) > 15) + throw new EqualException(expected, actual); + if (Math.Abs(expected.Y - actual.Y) > 15) + throw new EqualException(expected, actual); + } else { Assert.Equal(expected, actual); @@ -278,5 +351,34 @@ namespace Avalonia.IntegrationTests.Appium return elements.TakeWhile(x => x.FindElementByXPath("XCUIElementTypeWindow")?.GetAttribute("identifier") != identifier).Count(); } + + public static AppiumWebElement? FindWindowInner(this AppiumDriver session, string identifier) + { + return session.FindElementsByXPath("XCUIElementTypeWindow") + .FirstOrDefault(x => x.GetAttribute("identifier") == identifier); + } + + public static AppiumWebElement? FindWindowOuter(this AppiumDriver session, string identifier) + { + var windows = session.FindElementsByXPath("XCUIElementTypeWindow"); + + var window = windows.FirstOrDefault(x=>x.FindElementsByXPath("XCUIElementTypeWindow").Any(x => x.GetAttribute("identifier") == identifier)); + + return window; + } + + public static (AppiumWebElement? closeButton, AppiumWebElement? zoomButton, AppiumWebElement? miniturizeButton) GetChromeButtons (this AppiumWebElement outerWindow) + { + var closeButton = + outerWindow.FindElementByXPath("XCUIElementTypeButton[1]"); + + var zoomButton = + outerWindow.FindElementByXPath("XCUIElementTypeButton[2]"); + + var miniturizeButton = + outerWindow.FindElementByXPath("XCUIElementTypeButton[3]"); + + return (closeButton, zoomButton, miniturizeButton); + } } } From 827692fa272aaa841d352d5aef3d5b5f9c5614dd Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 2 Jun 2022 22:36:22 +0100 Subject: [PATCH 19/25] add test for osx modal dialog window order when clicking resize grip of parent. --- .../WindowTests.cs | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index b027c17ff3..59e705bd9b 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -3,9 +3,11 @@ using System.Collections.Generic; using System.Linq; using System.Reactive.Disposables; using System.Runtime.InteropServices; +using System.Threading.Tasks; using Avalonia.Controls; using OpenQA.Selenium; using OpenQA.Selenium.Appium; +using OpenQA.Selenium.Interactions; using SeleniumExtras.PageObjects; using Xunit; using Xunit.Sdk; @@ -50,7 +52,7 @@ namespace Avalonia.IntegrationTests.Appium { SwitchToNewWindowHack(mainWindow); var closeButton = _session.FindElementByAccessibilityId("CloseWindow"); - closeButton.SendClick(); + closeButton.Click(); } catch { } }); @@ -85,6 +87,43 @@ namespace Avalonia.IntegrationTests.Appium } } + [PlatformFact(SkipOnWindows = true)] + public void OSX_WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resize_Grip() + { + var mainWindowHandle = GetCurrentWindowHandleHack(); + + var mainWindow = + _session.FindWindowOuter("MainWindow"); + + try + { + using (OpenWindow(ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + { + new Actions(_session) + .MoveToElement(mainWindow, 100, 1) + .ClickAndHold() + .Perform(); + + var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); + + int mainWindowIndex = windows.GetWindowOrder("MainWindow"); + int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); + + new Actions(_session) + .MoveToElement(mainWindow, 100, 1) + .Release() + .Perform(); + + Assert.Equal(0, secondaryWindowIndex); + Assert.Equal(1, mainWindowIndex); + } + } + finally + { + SwitchToMainWindowHack(mainWindowHandle); + } + } + [PlatformFact(SkipOnWindows = true)] public void OSX_WindowOrder_Owned_Dialog_Stays_InFront_Of_Parent() { From a0af269d36b6300e941b1d9196746c08668528ec Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Thu, 2 Jun 2022 23:02:37 +0100 Subject: [PATCH 20/25] reset app after most tests, add test for fullscreen mode with modal osx. --- .../WindowTests.cs | 72 +++++++++++++++---- 1 file changed, 59 insertions(+), 13 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 59e705bd9b..8bf429c2ed 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -84,6 +84,7 @@ namespace Avalonia.IntegrationTests.Appium finally { SwitchToMainWindowHack(mainWindowHandle); + _session.ResetApp(); } } @@ -121,6 +122,41 @@ namespace Avalonia.IntegrationTests.Appium finally { SwitchToMainWindowHack(mainWindowHandle); + _session.ResetApp(); + } + } + + [PlatformFact(SkipOnWindows = true)] + public void OSX_WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_In_Fullscreen() + { + var mainWindowHandle = GetCurrentWindowHandleHack(); + + var mainWindow = + _session.FindWindowOuter("MainWindow"); + + var buttons = mainWindow.GetChromeButtons(); + + buttons.zoomButton.Click(); + + Task.Delay(500).Wait(); + + try + { + using (OpenWindow(ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + { + var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); + + int mainWindowIndex = windows.GetWindowOrder("MainWindow"); + int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); + + Assert.Equal(0, secondaryWindowIndex); + Assert.Equal(1, mainWindowIndex); + } + } + finally + { + SwitchToMainWindowHack(mainWindowHandle); + _session.ResetApp(); } } @@ -150,31 +186,39 @@ namespace Avalonia.IntegrationTests.Appium finally { SwitchToMainWindowHack(mainWindowHandle); + _session.ResetApp(); } } - + [PlatformFact(SkipOnWindows = true)] public void OSX_WindowOrder_NonOwned_Window_Does_Not_Stay_InFront_Of_Parent() { var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); - using (OpenWindow(ShowWindowMode.NonOwned, WindowStartupLocation.CenterOwner, 1400)) + try { - mainWindow.Click(); - - var secondaryWindow = - _session.FindElementByAccessibilityId("SecondaryWindow"); + using (OpenWindow(ShowWindowMode.NonOwned, WindowStartupLocation.CenterOwner, 1400)) + { + mainWindow.Click(); - var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); + var secondaryWindow = + _session.FindElementByAccessibilityId("SecondaryWindow"); - int mainWindowIndex = windows.GetWindowOrder("MainWindow"); - int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); + var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - Assert.Equal(1, secondaryWindowIndex); - Assert.Equal(0, mainWindowIndex); - - secondaryWindow.SendClick(); + int mainWindowIndex = windows.GetWindowOrder("MainWindow"); + int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); + + Assert.Equal(1, secondaryWindowIndex); + Assert.Equal(0, mainWindowIndex); + + secondaryWindow.SendClick(); + } + } + finally + { + _session.ResetApp(); } } @@ -206,6 +250,7 @@ namespace Avalonia.IntegrationTests.Appium finally { SwitchToMainWindowHack(mainWindowHandle); + _session.ResetApp(); } } @@ -233,6 +278,7 @@ namespace Avalonia.IntegrationTests.Appium finally { SwitchToMainWindowHack(mainWindowHandle); + _session.ResetApp(); } } From 860fcc524d4be5ad292ce09713f0dd9fd6ac7329 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 21 Jun 2022 13:46:35 +0200 Subject: [PATCH 21/25] Refactor how we show windows. Trying to make it a little less hacky. Only tested on Win32 so far. --- .../ElementExtensions.cs | 63 ++++++++ .../WindowTests.cs | 144 +++++++----------- 2 files changed, 114 insertions(+), 93 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs index 3eb8646835..07fea24a93 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs @@ -1,8 +1,12 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Reactive.Disposables; using System.Runtime.InteropServices; +using OpenQA.Selenium; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Interactions; +using Xunit; namespace Avalonia.IntegrationTests.Appium { @@ -43,6 +47,65 @@ namespace Avalonia.IntegrationTests.Appium } } + /// + /// Clicks a button which is expected to open a new window. + /// + /// The button to click. + /// + /// An object which when disposed will cause the newly opened window to close. + /// + public static IDisposable OpenWindowWithClick(this AppiumWebElement element) + { + var session = element.WrappedDriver; + var oldHandle = session.CurrentWindowHandle; + var oldHandles = session.WindowHandles.ToList(); + var oldChildWindows = session.FindElements(By.XPath("//Window")); + + element.Click(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var newHandle = session.WindowHandles.Except(oldHandles).SingleOrDefault(); + + if (newHandle is not null) + { + // A new top-level window was opened. We need to switch to it. + session.SwitchTo().Window(newHandle); + + return Disposable.Create(() => + { + session.Close(); + session.SwitchTo().Window(oldHandle); + }); + } + else + { + // If a new window handle hasn't been added to the session then it's likely + // that a child window was opened. These don't appear in session.WindowHandles + // so we have to use an XPath query to get hold of it. + var newChildWindows = session.FindElements(By.XPath("//Window")); + var childWindow = Assert.Single(newChildWindows.Except(oldChildWindows)); + + return Disposable.Create(() => + { + childWindow.SendKeys(Keys.Alt + Keys.F4 + Keys.Alt); + }); + } + } + else + { + var newHandle = session.CurrentWindowHandle; + + Assert.NotEqual(oldHandle, newHandle); + + return Disposable.Create(() => + { + session.Close(); + session.SwitchTo().Window(oldHandle); + }); + } + } + public static void SendClick(this AppiumWebElement element) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 8bf429c2ed..38d0d5ba1c 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -8,7 +8,6 @@ using Avalonia.Controls; using OpenQA.Selenium; using OpenQA.Selenium.Appium; using OpenQA.Selenium.Interactions; -using SeleniumExtras.PageObjects; using Xunit; using Xunit.Sdk; @@ -28,36 +27,33 @@ namespace Avalonia.IntegrationTests.Appium tab.Click(); } - private IDisposable OpenWindow(ShowWindowMode mode, WindowStartupLocation location, int width = 200, int height = 100) + [Theory] + [MemberData(nameof(StartupLocationData))] + public void StartupLocation(PixelSize? size, ShowWindowMode mode, WindowStartupLocation location) { - var mainWindow = GetCurrentWindowHandleHack(); - var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); - var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); - var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); - var showButton = _session.FindElementByAccessibilityId("ShowWindow"); - - sizeTextBox.SendKeys($"{width}, {height}"); + using var window = OpenWindow(size, mode, location); + var clientSize = Size.Parse(_session.FindElementByAccessibilityId("ClientSize").Text); + var frameSize = Size.Parse(_session.FindElementByAccessibilityId("FrameSize").Text); + var position = PixelPoint.Parse(_session.FindElementByAccessibilityId("Position").Text); + var screenRect = PixelRect.Parse(_session.FindElementByAccessibilityId("ScreenRect").Text); + var scaling = double.Parse(_session.FindElementByAccessibilityId("Scaling").Text); - modeComboBox.Click(); - _session.FindElementByName(mode.ToString()).SendClick(); + Assert.True(frameSize.Width >= clientSize.Width, "Expected frame width >= client width."); + Assert.True(frameSize.Height > clientSize.Height, "Expected frame height > client height."); - locationComboBox.Click(); - _session.FindElementByName(location.ToString()).SendClick(); + var frameRect = new PixelRect(position, PixelSize.FromSize(frameSize, scaling)); - showButton.Click(); - - return Disposable.Create(() => + switch (location) { - try - { - SwitchToNewWindowHack(mainWindow); - var closeButton = _session.FindElementByAccessibilityId("CloseWindow"); - closeButton.Click(); - } - catch { } - }); + case WindowStartupLocation.CenterScreen: + { + var expected = screenRect.CenterRect(frameRect); + AssertCloseEnough(expected.Position, frameRect.Position); + break; + } + } } - + [PlatformFact(SkipOnWindows = true)] public void OSX_WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent() { @@ -68,7 +64,7 @@ namespace Avalonia.IntegrationTests.Appium try { - using (OpenWindow(ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) { mainWindow.Click(); @@ -98,7 +94,7 @@ namespace Avalonia.IntegrationTests.Appium try { - using (OpenWindow(ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) { new Actions(_session) .MoveToElement(mainWindow, 100, 1) @@ -142,7 +138,7 @@ namespace Avalonia.IntegrationTests.Appium try { - using (OpenWindow(ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) { var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); @@ -170,7 +166,7 @@ namespace Avalonia.IntegrationTests.Appium try { - using (OpenWindow(ShowWindowMode.Owned, WindowStartupLocation.CenterOwner)) + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Owned, WindowStartupLocation.CenterOwner)) { mainWindow.Click(); @@ -198,7 +194,7 @@ namespace Avalonia.IntegrationTests.Appium try { - using (OpenWindow(ShowWindowMode.NonOwned, WindowStartupLocation.CenterOwner, 1400)) + using (OpenWindow(new PixelSize(1400, 100), ShowWindowMode.NonOwned, WindowStartupLocation.CenterOwner)) { mainWindow.Click(); @@ -238,7 +234,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.True(zoomButton.Enabled); Assert.True(miniturizeButton.Enabled); - using (OpenWindow(ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) { SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle); @@ -261,7 +257,7 @@ namespace Avalonia.IntegrationTests.Appium try { - using (OpenWindow(ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) { var secondaryWindow = _session.FindWindowOuter("SecondaryWindow"); @@ -281,69 +277,11 @@ namespace Avalonia.IntegrationTests.Appium _session.ResetApp(); } } - - [Theory] - [MemberData(nameof(StartupLocationData))] - public void StartupLocation(string? size, ShowWindowMode mode, WindowStartupLocation location) - { - var mainWindowHandle = GetCurrentWindowHandleHack(); - - try - { - var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); - var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); - var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); - var showButton = _session.FindElementByAccessibilityId("ShowWindow"); - - if (size is not null) - sizeTextBox.SendKeys(size); - - modeComboBox.Click(); - _session.FindElementByName(mode.ToString()).SendClick(); - - locationComboBox.Click(); - _session.FindElementByName(location.ToString()).SendClick(); - - showButton.Click(); - SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle); - - var clientSize = Size.Parse(_session.FindElementByAccessibilityId("ClientSize").Text); - var frameSize = Size.Parse(_session.FindElementByAccessibilityId("FrameSize").Text); - var position = PixelPoint.Parse(_session.FindElementByAccessibilityId("Position").Text); - var screenRect = PixelRect.Parse(_session.FindElementByAccessibilityId("ScreenRect").Text); - var scaling = double.Parse(_session.FindElementByAccessibilityId("Scaling").Text); - - Assert.True(frameSize.Width >= clientSize.Width, "Expected frame width >= client width."); - Assert.True(frameSize.Height > clientSize.Height, "Expected frame height > client height."); - - var frameRect = new PixelRect(position, PixelSize.FromSize(frameSize, scaling)); - - switch (location) - { - case WindowStartupLocation.CenterScreen: - { - var expected = screenRect.CenterRect(frameRect); - AssertCloseEnough(expected.Position, frameRect.Position); - break; - } - } - } - finally - { - try - { - var closeButton = _session.FindElementByAccessibilityId("CloseWindow"); - closeButton.Click(); - SwitchToMainWindowHack(mainWindowHandle); - } - catch { } - } - } - public static TheoryData StartupLocationData() + public static TheoryData StartupLocationData() { - var sizes = new[] { null, "400,300" }; - var data = new TheoryData(); + var sizes = new PixelSize?[] { null, new PixelSize(400, 300) }; + var data = new TheoryData(); foreach (var size in sizes) { @@ -421,6 +359,26 @@ namespace Avalonia.IntegrationTests.Appium } } + private IDisposable OpenWindow(PixelSize? size, ShowWindowMode mode, WindowStartupLocation location) + { + var mainWindow = GetCurrentWindowHandleHack(); + var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); + var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); + var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); + var showButton = _session.FindElementByAccessibilityId("ShowWindow"); + + if (size.HasValue) + sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}"); + + modeComboBox.Click(); + _session.FindElementByName(mode.ToString()).SendClick(); + + locationComboBox.Click(); + _session.FindElementByName(location.ToString()).SendClick(); + + return showButton.OpenWindowWithClick(); + } + public enum ShowWindowMode { NonOwned, From 8c3424baa4e0beeb51a4b8a65fd7055be8507cd1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 22 Jun 2022 10:36:49 +0200 Subject: [PATCH 22/25] Refactor OSX window tests a bit. Try not to rely on `_session.ResetApp();` because restoring the application state in the tests tends to show up more errors (which this change has done: https://github.com/AvaloniaUI/Avalonia/issues/8335#issuecomment-1162804733) --- samples/IntegrationTestApp/MainWindow.axaml | 2 + .../IntegrationTestApp/MainWindow.axaml.cs | 15 ++ .../IntegrationTestApp/ShowWindowTest.axaml | 2 - .../ShowWindowTest.axaml.cs | 2 - .../ElementExtensions.cs | 42 +++- .../WindowTests.cs | 210 ++++++------------ 6 files changed, 116 insertions(+), 157 deletions(-) diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 82348691e9..aa2191c26b 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -110,6 +110,8 @@ CenterOwner + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 580548a433..1aba10ec30 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Avalonia.VisualTree; @@ -82,6 +83,16 @@ namespace IntegrationTestApp } } + private void SendToBack() + { + var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!; + + foreach (var window in lifetime.Windows) + { + window.Activate(); + } + } + private void MenuClicked(object? sender, RoutedEventArgs e) { var clickedMenuItemTextBlock = this.FindControl("ClickedMenuItem"); @@ -102,6 +113,10 @@ namespace IntegrationTestApp this.FindControl("ClickedMenuItem").Text = "None"; if (source?.Name == "ShowWindow") ShowWindow(); + if (source?.Name == "SendToBack") + SendToBack(); + if (source?.Name == "ExitFullscreen") + WindowState = WindowState.Normal; } } } diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml index 2525bfc866..a263d8ab46 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml @@ -4,8 +4,6 @@ Name="SecondaryWindow" Title="Show Window Test"> - - diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs index 3f45f1c5ad..720f7b1c12 100644 --- a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs @@ -34,7 +34,5 @@ namespace IntegrationTestApp ownerRect.Text = $"{owner.Position}, {owner.FrameSize}"; } } - - private void CloseWindow_Click(object sender, RoutedEventArgs e) => Close(); } } diff --git a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs index 07fea24a93..16d37e4beb 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ElementExtensions.cs @@ -15,6 +15,19 @@ namespace Avalonia.IntegrationTests.Appium public static IReadOnlyList GetChildren(this AppiumWebElement element) => element.FindElementsByXPath("*/*"); + public static (AppiumWebElement close, AppiumWebElement minimize, AppiumWebElement maximize) GetChromeButtons(this AppiumWebElement window) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var closeButton = window.FindElementByXPath("//XCUIElementTypeButton[1]"); + var fullscreenButton = window.FindElementByXPath("//XCUIElementTypeButton[2]"); + var minimizeButton = window.FindElementByXPath("//XCUIElementTypeButton[3]"); + return (closeButton, minimizeButton, fullscreenButton); + } + + throw new NotSupportedException("GetChromeButtons not supported on this platform."); + } + public static string GetComboBoxValue(this AppiumWebElement element) { return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? @@ -57,14 +70,15 @@ namespace Avalonia.IntegrationTests.Appium public static IDisposable OpenWindowWithClick(this AppiumWebElement element) { var session = element.WrappedDriver; - var oldHandle = session.CurrentWindowHandle; - var oldHandles = session.WindowHandles.ToList(); - var oldChildWindows = session.FindElements(By.XPath("//Window")); - - element.Click(); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { + var oldHandle = session.CurrentWindowHandle; + var oldHandles = session.WindowHandles.ToList(); + var oldChildWindows = session.FindElements(By.XPath("//Window")); + + element.Click(); + var newHandle = session.WindowHandles.Except(oldHandles).SingleOrDefault(); if (newHandle is not null) @@ -94,14 +108,22 @@ namespace Avalonia.IntegrationTests.Appium } else { - var newHandle = session.CurrentWindowHandle; - - Assert.NotEqual(oldHandle, newHandle); + var oldWindows = session.FindElements(By.XPath("/XCUIElementTypeApplication/XCUIElementTypeWindow")); + var oldWindowTitles = oldWindows.ToDictionary(x => x.Text); + + element.Click(); + 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(() => { - session.Close(); - session.SwitchTo().Window(oldHandle); + // 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(); + close!.Click(); }); } } diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index 38d0d5ba1c..e887a2afe8 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -57,82 +57,56 @@ namespace Avalonia.IntegrationTests.Appium [PlatformFact(SkipOnWindows = true)] public void OSX_WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent() { - var mainWindowHandle = GetCurrentWindowHandleHack(); - - var mainWindow = - _session.FindElementByAccessibilityId("MainWindow"); + var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); - try + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) { - using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) - { - mainWindow.Click(); + mainWindow.Click(); - var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); + var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - int mainWindowIndex = windows.GetWindowOrder("MainWindow"); - int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); + int mainWindowIndex = windows.GetWindowOrder("MainWindow"); + int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); - Assert.Equal(0, secondaryWindowIndex); - Assert.Equal(1, mainWindowIndex); - } - } - finally - { - SwitchToMainWindowHack(mainWindowHandle); - _session.ResetApp(); + Assert.Equal(0, secondaryWindowIndex); + Assert.Equal(1, mainWindowIndex); } } [PlatformFact(SkipOnWindows = true)] public void OSX_WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resize_Grip() { - var mainWindowHandle = GetCurrentWindowHandleHack(); - - var mainWindow = - _session.FindWindowOuter("MainWindow"); + var mainWindow = _session.FindWindowOuter("MainWindow"); - try + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) { - using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) - { - new Actions(_session) - .MoveToElement(mainWindow, 100, 1) - .ClickAndHold() - .Perform(); + new Actions(_session) + .MoveToElement(mainWindow, 100, 1) + .ClickAndHold() + .Perform(); - var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); + var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - int mainWindowIndex = windows.GetWindowOrder("MainWindow"); - int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); + int mainWindowIndex = windows.GetWindowOrder("MainWindow"); + int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); - new Actions(_session) - .MoveToElement(mainWindow, 100, 1) - .Release() - .Perform(); + new Actions(_session) + .MoveToElement(mainWindow, 100, 1) + .Release() + .Perform(); - Assert.Equal(0, secondaryWindowIndex); - Assert.Equal(1, mainWindowIndex); - } - } - finally - { - SwitchToMainWindowHack(mainWindowHandle); - _session.ResetApp(); + Assert.Equal(0, secondaryWindowIndex); + Assert.Equal(1, mainWindowIndex); } } [PlatformFact(SkipOnWindows = true)] public void OSX_WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_In_Fullscreen() { - var mainWindowHandle = GetCurrentWindowHandleHack(); - - var mainWindow = - _session.FindWindowOuter("MainWindow"); - + var mainWindow = _session.FindWindowOuter("MainWindow"); var buttons = mainWindow.GetChromeButtons(); - buttons.zoomButton.Click(); + buttons.maximize.Click(); Task.Delay(500).Wait(); @@ -151,70 +125,51 @@ namespace Avalonia.IntegrationTests.Appium } finally { - SwitchToMainWindowHack(mainWindowHandle); - _session.ResetApp(); + _session.FindElementByAccessibilityId("ExitFullscreen").Click(); } } [PlatformFact(SkipOnWindows = true)] public void OSX_WindowOrder_Owned_Dialog_Stays_InFront_Of_Parent() { - var mainWindowHandle = GetCurrentWindowHandleHack(); - - var mainWindow = - _session.FindElementByAccessibilityId("MainWindow"); + var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); - try + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Owned, WindowStartupLocation.CenterOwner)) { - using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Owned, WindowStartupLocation.CenterOwner)) - { - mainWindow.Click(); + mainWindow.Click(); - var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); + var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - int mainWindowIndex = windows.GetWindowOrder("MainWindow"); - int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); + int mainWindowIndex = windows.GetWindowOrder("MainWindow"); + int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); - Assert.Equal(0, secondaryWindowIndex); - Assert.Equal(1, mainWindowIndex); - } - } - finally - { - SwitchToMainWindowHack(mainWindowHandle); - _session.ResetApp(); + Assert.Equal(0, secondaryWindowIndex); + Assert.Equal(1, mainWindowIndex); } } [PlatformFact(SkipOnWindows = true)] public void OSX_WindowOrder_NonOwned_Window_Does_Not_Stay_InFront_Of_Parent() { - var mainWindow = - _session.FindElementByAccessibilityId("MainWindow"); + var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); - try + using (OpenWindow(new PixelSize(1400, 100), ShowWindowMode.NonOwned, WindowStartupLocation.CenterOwner)) { - using (OpenWindow(new PixelSize(1400, 100), ShowWindowMode.NonOwned, WindowStartupLocation.CenterOwner)) - { - mainWindow.Click(); + mainWindow.Click(); - var secondaryWindow = - _session.FindElementByAccessibilityId("SecondaryWindow"); + var secondaryWindow = + _session.FindElementByAccessibilityId("SecondaryWindow"); - var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); + var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - int mainWindowIndex = windows.GetWindowOrder("MainWindow"); - int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); + int mainWindowIndex = windows.GetWindowOrder("MainWindow"); + int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); - Assert.Equal(1, secondaryWindowIndex); - Assert.Equal(0, mainWindowIndex); + Assert.Equal(1, secondaryWindowIndex); + Assert.Equal(0, mainWindowIndex); - secondaryWindow.SendClick(); - } - } - finally - { - _session.ResetApp(); + var sendToBack = _session.FindElementByAccessibilityId("SendToBack"); + sendToBack.Click(); } } @@ -222,31 +177,22 @@ namespace Avalonia.IntegrationTests.Appium public void OSX_Parent_Window_Has_Disabled_ChromeButtons_When_Modal_Dialog_Shown() { var mainWindowHandle = GetCurrentWindowHandleHack(); - - try - { - var window = _session.FindWindowOuter("MainWindow"); + var window = _session.FindWindowOuter("MainWindow"); - var (closeButton, zoomButton, miniturizeButton) - = window.GetChromeButtons(); + var (closeButton, zoomButton, miniturizeButton) + = window.GetChromeButtons(); - Assert.True(closeButton.Enabled); - Assert.True(zoomButton.Enabled); - Assert.True(miniturizeButton.Enabled); + Assert.True(closeButton.Enabled); + Assert.True(zoomButton.Enabled); + Assert.True(miniturizeButton.Enabled); - using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) - { - SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle); - - Assert.False(closeButton.Enabled); - Assert.False(zoomButton.Enabled); - Assert.False(miniturizeButton.Enabled); - } - } - finally + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) { - SwitchToMainWindowHack(mainWindowHandle); - _session.ResetApp(); + SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle); + + Assert.False(closeButton.Enabled); + Assert.False(zoomButton.Enabled); + Assert.False(miniturizeButton.Enabled); } } @@ -255,26 +201,18 @@ namespace Avalonia.IntegrationTests.Appium { var mainWindowHandle = GetCurrentWindowHandleHack(); - try + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) { - using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) - { - var secondaryWindow = _session.FindWindowOuter("SecondaryWindow"); + var secondaryWindow = _session.FindWindowOuter("SecondaryWindow"); - var (closeButton, zoomButton, miniturizeButton) - = secondaryWindow.GetChromeButtons(); - - Assert.True(closeButton.Enabled); - Assert.True(zoomButton.Enabled); - Assert.False(miniturizeButton.Enabled); + var (closeButton, zoomButton, miniaturizeButton) + = secondaryWindow.GetChromeButtons(); - SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle); - } - } - finally - { - SwitchToMainWindowHack(mainWindowHandle); - _session.ResetApp(); + Assert.True(closeButton.Enabled); + Assert.True(zoomButton.Enabled); + Assert.False(miniaturizeButton.Enabled); + + SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle); } } @@ -409,19 +347,5 @@ namespace Avalonia.IntegrationTests.Appium return window; } - - public static (AppiumWebElement? closeButton, AppiumWebElement? zoomButton, AppiumWebElement? miniturizeButton) GetChromeButtons (this AppiumWebElement outerWindow) - { - var closeButton = - outerWindow.FindElementByXPath("XCUIElementTypeButton[1]"); - - var zoomButton = - outerWindow.FindElementByXPath("XCUIElementTypeButton[2]"); - - var miniturizeButton = - outerWindow.FindElementByXPath("XCUIElementTypeButton[3]"); - - return (closeButton, zoomButton, miniturizeButton); - } } } From 8b9e675bd4a8ee174c78d54062c1c0b04a22b845 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 22 Jun 2022 10:55:19 +0200 Subject: [PATCH 23/25] Refactor PlatformFact. To make it inclusive rather than exclusive. --- .../ButtonTests.cs | 2 +- .../ComboBoxTests.cs | 6 ++-- .../ListBoxTests.cs | 2 +- .../MenuTests.cs | 16 +++++----- .../NativeMenuTests.cs | 2 +- .../PlatformFactAttribute.cs | 32 +++++++++++++------ .../WindowTests.cs | 14 ++++---- 7 files changed, 43 insertions(+), 31 deletions(-) diff --git a/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs b/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs index 2ac859e091..6c630ae782 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ButtonTests.cs @@ -44,7 +44,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("Button with TextBlock", button.Text); } - [PlatformFact(SkipOnOSX = true)] + [PlatformFact(TestPlatforms.Windows)] public void ButtonWithAcceleratorKey() { var button = _session.FindElementByAccessibilityId("ButtonWithAcceleratorKey"); diff --git a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs index fad3e1eb9f..abdb4e2dd8 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ComboBoxTests.cs @@ -46,7 +46,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("Item 0", comboBox.GetComboBoxValue()); } - [PlatformFact(SkipOnOSX = true)] + [PlatformFact(TestPlatforms.Windows)] public void Can_Change_Selection_With_Keyboard() { var comboBox = _session.FindElementByAccessibilityId("BasicComboBox"); @@ -63,7 +63,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("Item 1", comboBox.GetComboBoxValue()); } - [PlatformFact(SkipOnOSX = true)] + [PlatformFact(TestPlatforms.Windows)] public void Can_Change_Selection_With_Keyboard_From_Unselected() { var comboBox = _session.FindElementByAccessibilityId("BasicComboBox"); @@ -80,7 +80,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("Item 0", comboBox.GetComboBoxValue()); } - [PlatformFact(SkipOnOSX = true)] + [PlatformFact(TestPlatforms.Windows)] public void Can_Cancel_Keyboard_Selection_With_Escape() { var comboBox = _session.FindElementByAccessibilityId("BasicComboBox"); diff --git a/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs b/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs index 625742ac20..e2943b3349 100644 --- a/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/ListBoxTests.cs @@ -61,7 +61,7 @@ namespace Avalonia.IntegrationTests.Appium } // appium-mac2-driver just hangs - [PlatformFact(SkipOnOSX = true)] + [PlatformFact(TestPlatforms.Windows)] public void Can_Select_Range_By_Shift_Clicking() { var listBox = GetTarget(); diff --git a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs index 98fb335061..d1d231466f 100644 --- a/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/MenuTests.cs @@ -57,7 +57,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("_Grandchild", clickedMenuItem.Text); } - [PlatformFact(SkipOnOSX = true)] + [PlatformFact(TestPlatforms.Windows)] public void Select_Child_With_Alt_Arrow_Keys() { new Actions(_session) @@ -69,7 +69,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("_Child 1", clickedMenuItem.Text); } - [PlatformFact(SkipOnOSX = true)] + [PlatformFact(TestPlatforms.Windows)] public void Select_Grandchild_With_Alt_Arrow_Keys() { new Actions(_session) @@ -81,7 +81,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("_Grandchild", clickedMenuItem.Text); } - [PlatformFact(SkipOnOSX = true)] + [PlatformFact(TestPlatforms.Windows)] public void Select_Child_With_Alt_Access_Keys() { new Actions(_session) @@ -93,7 +93,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("_Child 1", clickedMenuItem.Text); } - [PlatformFact(SkipOnOSX = true)] + [PlatformFact(TestPlatforms.Windows)] public void Select_Grandchild_With_Alt_Access_Keys() { new Actions(_session) @@ -105,7 +105,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("_Grandchild", clickedMenuItem.Text); } - [PlatformFact(SkipOnOSX = true)] + [PlatformFact(TestPlatforms.Windows)] public void Select_Child_With_Click_Arrow_Keys() { var rootMenuItem = _session.FindElementByAccessibilityId("RootMenuItem"); @@ -119,7 +119,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("_Child 1", clickedMenuItem.Text); } - [PlatformFact(SkipOnOSX = true)] + [PlatformFact(TestPlatforms.Windows)] public void Select_Grandchild_With_Click_Arrow_Keys() { var rootMenuItem = _session.FindElementByAccessibilityId("RootMenuItem"); @@ -133,7 +133,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("_Grandchild", clickedMenuItem.Text); } - [PlatformFact(SkipOnOSX = true)] + [PlatformFact(TestPlatforms.Windows)] public void Child_AcceleratorKey() { var rootMenuItem = _session.FindElementByAccessibilityId("RootMenuItem"); @@ -145,7 +145,7 @@ namespace Avalonia.IntegrationTests.Appium Assert.Equal("Ctrl+O", childMenuItem.GetAttribute("AcceleratorKey")); } - [PlatformFact(SkipOnOSX = true)] + [PlatformFact(TestPlatforms.Windows)] public void PointerOver_Does_Not_Steal_Focus() { // Issue #7906 diff --git a/tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs b/tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs index fde01f0e41..7858c4cc81 100644 --- a/tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs @@ -17,7 +17,7 @@ namespace Avalonia.IntegrationTests.Appium tab.Click(); } - [PlatformFact(SkipOnWindows = true)] + [PlatformFact(TestPlatforms.MacOS)] public void View_Menu_Select_Button_Tab() { var tabs = _session.FindElementByAccessibilityId("MainTabs"); diff --git a/tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs b/tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs index 60338b92c2..53ae5d924f 100644 --- a/tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs +++ b/tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs @@ -5,21 +5,33 @@ using Xunit; namespace Avalonia.IntegrationTests.Appium { + [Flags] + internal enum TestPlatforms + { + Windows = 0x01, + MacOS = 0x02, + All = Windows | MacOS, + } + internal class PlatformFactAttribute : FactAttribute { + public PlatformFactAttribute(TestPlatforms platforms = TestPlatforms.All) => Platforms = platforms; + + public TestPlatforms Platforms { get; } + public override string? Skip { - get - { - if (SkipOnWindows && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - return "Ignored on Windows"; - if (SkipOnOSX && RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - return "Ignored on MacOS"; - return null; - } + get => IsSupported() ? null : $"Ignored on {RuntimeInformation.OSDescription}"; set => throw new NotSupportedException(); } - public bool SkipOnOSX { get; set; } - public bool SkipOnWindows { get; set; } + + private bool IsSupported() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return Platforms.HasAnyFlag(TestPlatforms.Windows); + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return Platforms.HasAnyFlag(TestPlatforms.MacOS); + return false; + } } } diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index e887a2afe8..b759dafbeb 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -54,7 +54,7 @@ namespace Avalonia.IntegrationTests.Appium } } - [PlatformFact(SkipOnWindows = true)] + [PlatformFact(TestPlatforms.MacOS)] public void OSX_WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent() { var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); @@ -73,7 +73,7 @@ namespace Avalonia.IntegrationTests.Appium } } - [PlatformFact(SkipOnWindows = true)] + [PlatformFact(TestPlatforms.MacOS)] public void OSX_WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resize_Grip() { var mainWindow = _session.FindWindowOuter("MainWindow"); @@ -100,7 +100,7 @@ namespace Avalonia.IntegrationTests.Appium } } - [PlatformFact(SkipOnWindows = true)] + [PlatformFact(TestPlatforms.MacOS)] public void OSX_WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_In_Fullscreen() { var mainWindow = _session.FindWindowOuter("MainWindow"); @@ -129,7 +129,7 @@ namespace Avalonia.IntegrationTests.Appium } } - [PlatformFact(SkipOnWindows = true)] + [PlatformFact(TestPlatforms.MacOS)] public void OSX_WindowOrder_Owned_Dialog_Stays_InFront_Of_Parent() { var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); @@ -148,7 +148,7 @@ namespace Avalonia.IntegrationTests.Appium } } - [PlatformFact(SkipOnWindows = true)] + [PlatformFact(TestPlatforms.MacOS)] public void OSX_WindowOrder_NonOwned_Window_Does_Not_Stay_InFront_Of_Parent() { var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); @@ -173,7 +173,7 @@ namespace Avalonia.IntegrationTests.Appium } } - [PlatformFact(SkipOnWindows = true)] + [PlatformFact(TestPlatforms.MacOS)] public void OSX_Parent_Window_Has_Disabled_ChromeButtons_When_Modal_Dialog_Shown() { var mainWindowHandle = GetCurrentWindowHandleHack(); @@ -196,7 +196,7 @@ namespace Avalonia.IntegrationTests.Appium } } - [PlatformFact(SkipOnWindows = true)] + [PlatformFact(TestPlatforms.MacOS)] public void OSX_Minimize_Button_Disabled_Modal_Dialog() { var mainWindowHandle = GetCurrentWindowHandleHack(); From 4f4f291b2d3220c59ec8ac80db1b31608fd1003d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 24 Jun 2022 10:55:30 +0200 Subject: [PATCH 24/25] Move mac-specific tests to a separate file. And tidy stuff up a bit. --- .../WindowTests.cs | 226 ------------------ .../WindowTests_MacOS.cs | 210 ++++++++++++++++ 2 files changed, 210 insertions(+), 226 deletions(-) create mode 100644 tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs index b759dafbeb..f1a625dbb4 100644 --- a/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests.cs @@ -1,13 +1,7 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Reactive.Disposables; using System.Runtime.InteropServices; -using System.Threading.Tasks; using Avalonia.Controls; -using OpenQA.Selenium; using OpenQA.Selenium.Appium; -using OpenQA.Selenium.Interactions; using Xunit; using Xunit.Sdk; @@ -53,168 +47,6 @@ namespace Avalonia.IntegrationTests.Appium } } } - - [PlatformFact(TestPlatforms.MacOS)] - public void OSX_WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent() - { - var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); - - using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) - { - mainWindow.Click(); - - var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - - int mainWindowIndex = windows.GetWindowOrder("MainWindow"); - int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); - - Assert.Equal(0, secondaryWindowIndex); - Assert.Equal(1, mainWindowIndex); - } - } - - [PlatformFact(TestPlatforms.MacOS)] - public void OSX_WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resize_Grip() - { - var mainWindow = _session.FindWindowOuter("MainWindow"); - - using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) - { - new Actions(_session) - .MoveToElement(mainWindow, 100, 1) - .ClickAndHold() - .Perform(); - - var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - - int mainWindowIndex = windows.GetWindowOrder("MainWindow"); - int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); - - new Actions(_session) - .MoveToElement(mainWindow, 100, 1) - .Release() - .Perform(); - - Assert.Equal(0, secondaryWindowIndex); - Assert.Equal(1, mainWindowIndex); - } - } - - [PlatformFact(TestPlatforms.MacOS)] - public void OSX_WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_In_Fullscreen() - { - var mainWindow = _session.FindWindowOuter("MainWindow"); - var buttons = mainWindow.GetChromeButtons(); - - buttons.maximize.Click(); - - Task.Delay(500).Wait(); - - try - { - using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) - { - var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - - int mainWindowIndex = windows.GetWindowOrder("MainWindow"); - int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); - - Assert.Equal(0, secondaryWindowIndex); - Assert.Equal(1, mainWindowIndex); - } - } - finally - { - _session.FindElementByAccessibilityId("ExitFullscreen").Click(); - } - } - - [PlatformFact(TestPlatforms.MacOS)] - public void OSX_WindowOrder_Owned_Dialog_Stays_InFront_Of_Parent() - { - var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); - - using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Owned, WindowStartupLocation.CenterOwner)) - { - mainWindow.Click(); - - var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - - int mainWindowIndex = windows.GetWindowOrder("MainWindow"); - int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); - - Assert.Equal(0, secondaryWindowIndex); - Assert.Equal(1, mainWindowIndex); - } - } - - [PlatformFact(TestPlatforms.MacOS)] - public void OSX_WindowOrder_NonOwned_Window_Does_Not_Stay_InFront_Of_Parent() - { - var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); - - using (OpenWindow(new PixelSize(1400, 100), ShowWindowMode.NonOwned, WindowStartupLocation.CenterOwner)) - { - mainWindow.Click(); - - var secondaryWindow = - _session.FindElementByAccessibilityId("SecondaryWindow"); - - var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); - - int mainWindowIndex = windows.GetWindowOrder("MainWindow"); - int secondaryWindowIndex = windows.GetWindowOrder("SecondaryWindow"); - - Assert.Equal(1, secondaryWindowIndex); - Assert.Equal(0, mainWindowIndex); - - var sendToBack = _session.FindElementByAccessibilityId("SendToBack"); - sendToBack.Click(); - } - } - - [PlatformFact(TestPlatforms.MacOS)] - public void OSX_Parent_Window_Has_Disabled_ChromeButtons_When_Modal_Dialog_Shown() - { - var mainWindowHandle = GetCurrentWindowHandleHack(); - var window = _session.FindWindowOuter("MainWindow"); - - var (closeButton, zoomButton, miniturizeButton) - = window.GetChromeButtons(); - - Assert.True(closeButton.Enabled); - Assert.True(zoomButton.Enabled); - Assert.True(miniturizeButton.Enabled); - - using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) - { - SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle); - - Assert.False(closeButton.Enabled); - Assert.False(zoomButton.Enabled); - Assert.False(miniturizeButton.Enabled); - } - } - - [PlatformFact(TestPlatforms.MacOS)] - public void OSX_Minimize_Button_Disabled_Modal_Dialog() - { - var mainWindowHandle = GetCurrentWindowHandleHack(); - - using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) - { - var secondaryWindow = _session.FindWindowOuter("SecondaryWindow"); - - var (closeButton, zoomButton, miniaturizeButton) - = secondaryWindow.GetChromeButtons(); - - Assert.True(closeButton.Enabled); - Assert.True(zoomButton.Enabled); - Assert.False(miniaturizeButton.Enabled); - - SwitchToNewWindowHack(oldWindowHandle: mainWindowHandle); - } - } public static TheoryData StartupLocationData() { @@ -238,39 +70,6 @@ namespace Avalonia.IntegrationTests.Appium return data; } - private string? GetCurrentWindowHandleHack() - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // HACK: WinAppDriver only seems to switch to a newly opened window if the window has an owner, - // otherwise the session remains targeting the previous window. Return the handle for the - // current window so we know which window to switch to when another is opened. - return _session.WindowHandles.Single(); - } - - return null; - } - - private void SwitchToNewWindowHack(string? oldWindowHandle) - { - if (oldWindowHandle is not null) - { - var newWindowHandle = _session.WindowHandles.FirstOrDefault(x => x != oldWindowHandle); - - // HACK: Looks like WinAppDriver only adds window handles for non-owned windows, but luckily - // non-owned windows is where we're having the problem, so if we find a window handle that - // isn't the main window handle then switch to it. - if (newWindowHandle is not null) - _session.SwitchTo().Window(newWindowHandle); - } - } - - private void SwitchToMainWindowHack(string? mainWindowHandle) - { - if (mainWindowHandle is not null) - _session.SwitchTo().Window(mainWindowHandle); - } - private static void AssertCloseEnough(PixelPoint expected, PixelPoint actual) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -299,7 +98,6 @@ namespace Avalonia.IntegrationTests.Appium private IDisposable OpenWindow(PixelSize? size, ShowWindowMode mode, WindowStartupLocation location) { - var mainWindow = GetCurrentWindowHandleHack(); var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); @@ -324,28 +122,4 @@ namespace Avalonia.IntegrationTests.Appium Modal } } - - static class Extensions - { - public static int GetWindowOrder(this IReadOnlyCollection elements, string identifier) - { - return elements.TakeWhile(x => - x.FindElementByXPath("XCUIElementTypeWindow")?.GetAttribute("identifier") != identifier).Count(); - } - - public static AppiumWebElement? FindWindowInner(this AppiumDriver session, string identifier) - { - return session.FindElementsByXPath("XCUIElementTypeWindow") - .FirstOrDefault(x => x.GetAttribute("identifier") == identifier); - } - - public static AppiumWebElement? FindWindowOuter(this AppiumDriver session, string identifier) - { - var windows = session.FindElementsByXPath("XCUIElementTypeWindow"); - - var window = windows.FirstOrDefault(x=>x.FindElementsByXPath("XCUIElementTypeWindow").Any(x => x.GetAttribute("identifier") == identifier)); - - return window; - } - } } diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs new file mode 100644 index 0000000000..a460dc1d68 --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Avalonia.Controls; +using OpenQA.Selenium; +using OpenQA.Selenium.Appium; +using OpenQA.Selenium.Interactions; +using Xunit; + +namespace Avalonia.IntegrationTests.Appium +{ + [Collection("Default")] + public class WindowTests_MacOS + { + private readonly AppiumDriver _session; + + public WindowTests_MacOS(TestAppFixture fixture) + { + _session = fixture.Session; + + var tabs = _session.FindElementByAccessibilityId("MainTabs"); + var tab = tabs.FindElementByName("Window"); + tab.Click(); + } + + [PlatformFact(TestPlatforms.MacOS)] + public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent() + { + var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); + + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + { + mainWindow.Click(); + + var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); + var mainWindowIndex = GetWindowOrder(windows, "MainWindow"); + var secondaryWindowIndex = GetWindowOrder(windows, "SecondaryWindow"); + + Assert.Equal(0, secondaryWindowIndex); + Assert.Equal(1, mainWindowIndex); + } + } + + [PlatformFact(TestPlatforms.MacOS)] + public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_Clicking_Resize_Grip() + { + var mainWindow = FindWindow(_session, "MainWindow"); + + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + { + new Actions(_session) + .MoveToElement(mainWindow, 100, 1) + .ClickAndHold() + .Perform(); + + var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); + var mainWindowIndex = GetWindowOrder(windows, "MainWindow"); + var secondaryWindowIndex = GetWindowOrder(windows, "SecondaryWindow"); + + new Actions(_session) + .MoveToElement(mainWindow, 100, 1) + .Release() + .Perform(); + + Assert.Equal(0, secondaryWindowIndex); + Assert.Equal(1, mainWindowIndex); + } + } + + [PlatformFact(TestPlatforms.MacOS)] + public void WindowOrder_Modal_Dialog_Stays_InFront_Of_Parent_When_In_Fullscreen() + { + var mainWindow = FindWindow(_session, "MainWindow"); + var buttons = mainWindow.GetChromeButtons(); + + buttons.maximize.Click(); + + Thread.Sleep(500); + + try + { + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + { + var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); + var mainWindowIndex = GetWindowOrder(windows, "MainWindow"); + var secondaryWindowIndex = GetWindowOrder(windows, "SecondaryWindow"); + + Assert.Equal(0, secondaryWindowIndex); + Assert.Equal(1, mainWindowIndex); + } + } + finally + { + _session.FindElementByAccessibilityId("ExitFullscreen").Click(); + } + } + + [PlatformFact(TestPlatforms.MacOS)] + public void WindowOrder_Owned_Dialog_Stays_InFront_Of_Parent() + { + var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); + + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Owned, WindowStartupLocation.CenterOwner)) + { + mainWindow.Click(); + + var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); + var mainWindowIndex = GetWindowOrder(windows, "MainWindow"); + var secondaryWindowIndex = GetWindowOrder(windows, "SecondaryWindow"); + + Assert.Equal(0, secondaryWindowIndex); + Assert.Equal(1, mainWindowIndex); + } + } + + [PlatformFact(TestPlatforms.MacOS)] + public void WindowOrder_NonOwned_Window_Does_Not_Stay_InFront_Of_Parent() + { + var mainWindow = _session.FindElementByAccessibilityId("MainWindow"); + + using (OpenWindow(new PixelSize(1400, 100), ShowWindowMode.NonOwned, WindowStartupLocation.CenterOwner)) + { + mainWindow.Click(); + + var windows = _session.FindElements(By.XPath("XCUIElementTypeWindow")); + var mainWindowIndex = GetWindowOrder(windows, "MainWindow"); + var secondaryWindowIndex = GetWindowOrder(windows, "SecondaryWindow"); + + Assert.Equal(1, secondaryWindowIndex); + Assert.Equal(0, mainWindowIndex); + + var sendToBack = _session.FindElementByAccessibilityId("SendToBack"); + sendToBack.Click(); + } + } + + [PlatformFact(TestPlatforms.MacOS)] + public void Parent_Window_Has_Disabled_ChromeButtons_When_Modal_Dialog_Shown() + { + var window = FindWindow(_session, "MainWindow"); + var (closeButton, miniaturizeButton, zoomButton) = window.GetChromeButtons(); + + Assert.True(closeButton.Enabled); + Assert.True(zoomButton.Enabled); + Assert.True(miniaturizeButton.Enabled); + + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + { + Assert.False(closeButton.Enabled); + Assert.False(zoomButton.Enabled); + Assert.False(miniaturizeButton.Enabled); + } + } + + [PlatformFact(TestPlatforms.MacOS)] + public void Minimize_Button_Is_Disabled_On_Modal_Dialog() + { + using (OpenWindow(new PixelSize(200, 100), ShowWindowMode.Modal, WindowStartupLocation.CenterOwner)) + { + var secondaryWindow = FindWindow(_session, "SecondaryWindow"); + var (closeButton, miniaturizeButton, zoomButton) = secondaryWindow.GetChromeButtons(); + + Assert.True(closeButton.Enabled); + Assert.True(zoomButton.Enabled); + Assert.False(miniaturizeButton.Enabled); + } + } + + private IDisposable OpenWindow(PixelSize? size, ShowWindowMode mode, WindowStartupLocation location) + { + var sizeTextBox = _session.FindElementByAccessibilityId("ShowWindowSize"); + var modeComboBox = _session.FindElementByAccessibilityId("ShowWindowMode"); + var locationComboBox = _session.FindElementByAccessibilityId("ShowWindowLocation"); + var showButton = _session.FindElementByAccessibilityId("ShowWindow"); + + if (size.HasValue) + sizeTextBox.SendKeys($"{size.Value.Width}, {size.Value.Height}"); + + modeComboBox.Click(); + _session.FindElementByName(mode.ToString()).SendClick(); + + locationComboBox.Click(); + _session.FindElementByName(location.ToString()).SendClick(); + + return showButton.OpenWindowWithClick(); + } + + private static int GetWindowOrder(IReadOnlyCollection elements, string identifier) + { + return elements.TakeWhile(x => + x.FindElementByXPath("XCUIElementTypeWindow")?.GetAttribute("identifier") != identifier).Count(); + } + + private static AppiumWebElement FindWindow(AppiumDriver session, string identifier) + { + var windows = session.FindElementsByXPath("XCUIElementTypeWindow"); + return windows.First(x => + x.FindElementsByXPath("XCUIElementTypeWindow") + .Any(y => y.GetAttribute("identifier") == identifier)); + } + + public enum ShowWindowMode + { + NonOwned, + Owned, + Modal + } + } +} From be9c9cfaad64af21499f31fab2acda5091f01d43 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 24 Jun 2022 14:31:40 +0200 Subject: [PATCH 25/25] Remove debug code. --- src/Avalonia.Controls/WindowBase.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs index d5e54a5c08..1b25806892 100644 --- a/src/Avalonia.Controls/WindowBase.cs +++ b/src/Avalonia.Controls/WindowBase.cs @@ -225,8 +225,6 @@ namespace Avalonia.Controls LayoutManager.ExecuteLayoutPass(); Renderer?.Resized(clientSize); } - - System.Diagnostics.Debug.WriteLine($"HandleResized: ClientSize {ClientSize} | FrameSize {FrameSize}"); } ///