Przeglądaj źródła

Added integration tests for WindowStartupLocation.

Steven Kirk 3 lat temu
rodzic
commit
4c8240b7bf

+ 17 - 0
samples/IntegrationTestApp/MainWindow.axaml

@@ -94,6 +94,23 @@
           </StackPanel>
         </DockPanel>
       </TabItem>
+      
+      <TabItem Header="Window">
+        <StackPanel>
+          <TextBox Name="ShowWindowSize" Watermark="Window Size"/>
+          <ComboBox Name="ShowWindowMode" SelectedIndex="0">
+            <ComboBoxItem>NonOwned</ComboBoxItem>
+            <ComboBoxItem>Owned</ComboBoxItem>
+            <ComboBoxItem>Modal</ComboBoxItem>
+          </ComboBox>
+          <ComboBox Name="ShowWindowLocation" SelectedIndex="0">
+            <ComboBoxItem>Manual</ComboBoxItem>
+            <ComboBoxItem>CenterScreen</ComboBoxItem>
+            <ComboBoxItem>CenterOwner</ComboBoxItem>
+          </ComboBox>
+          <Button Name="ShowWindow">Show Window</Button>
+        </StackPanel>
+      </TabItem>
     </TabControl>
   </DockPanel>
 </Window>

+ 38 - 0
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<TextBox>("ShowWindowSize");
+            var modeComboBox = this.GetControl<ComboBox>("ShowWindowMode");
+            var locationComboBox = this.GetControl<ComboBox>("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<TextBlock>("ClickedMenuItem");
@@ -64,6 +100,8 @@ namespace IntegrationTestApp
                 this.FindControl<ListBox>("BasicListBox").SelectedIndex = -1;
             if (source?.Name == "MenuClickedMenuItemReset")
                 this.FindControl<TextBlock>("ClickedMenuItem").Text = "None";
+            if (source?.Name == "ShowWindow")
+                ShowWindow();
         }
     }
 }

+ 26 - 0
samples/IntegrationTestApp/ShowWindowTest.axaml

@@ -0,0 +1,26 @@
+<Window xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        x:Class="IntegrationTestApp.ShowWindowTest"
+        Title="Show Window Test">
+  <Grid ColumnDefinitions="Auto,Auto" RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto">
+    <Button Name="CloseWindow" Grid.Row="0" Click="CloseWindow_Click">Close</Button>
+
+    <Label Grid.Column="0" Grid.Row="1">Client Size</Label>
+    <TextBox Name="ClientSize" Grid.Column="1" Grid.Row="1" IsReadOnly="True"/>
+    
+    <Label Grid.Column="0" Grid.Row="2">Frame Size</Label>
+    <TextBox Name="FrameSize" Grid.Column="1" Grid.Row="2" IsReadOnly="True"/>
+
+    <Label Grid.Column="0" Grid.Row="3">Position</Label>
+    <TextBox Name="Position" Grid.Column="1" Grid.Row="3" IsReadOnly="True"/>
+
+    <Label Grid.Column="0" Grid.Row="4">Owner Rect</Label>
+    <TextBox Name="OwnerRect" Grid.Column="1" Grid.Row="4" IsReadOnly="True"/>
+    
+    <Label Grid.Column="0" Grid.Row="5">Screen Rect</Label>
+    <TextBox Name="ScreenRect" Grid.Column="1" Grid.Row="5" IsReadOnly="True"/>
+
+    <Label Grid.Column="0" Grid.Row="6">Scaling</Label>
+    <TextBox Name="Scaling" Grid.Column="1" Grid.Row="6" IsReadOnly="True"/>
+  </Grid>
+</Window>

+ 40 - 0
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<TextBox>("ClientSize").Text = $"{Width}, {Height}";
+            this.GetControl<TextBox>("FrameSize").Text = $"{FrameSize}";
+            this.GetControl<TextBox>("Position").Text = $"{Position}";
+            this.GetControl<TextBox>("ScreenRect").Text = $"{Screens.ScreenFromVisual(this)?.WorkingArea}";
+            this.GetControl<TextBox>("Scaling").Text = $"{((IRenderRoot)this).RenderScaling}";
+
+            if (Owner is not null)
+            {
+                var ownerRect = this.GetControl<TextBox>("OwnerRect");
+                var owner = (Window)Owner;
+                ownerRect.Text = $"{owner.Position}, {owner.FrameSize}";
+            }
+        }
+
+        private void CloseWindow_Click(object sender, RoutedEventArgs e) => Close();
+    }
+}

+ 4 - 0
tests/Avalonia.IntegrationTests.Appium/Avalonia.IntegrationTests.Appium.csproj

@@ -5,6 +5,10 @@
     <Nullable>enable</Nullable>
   </PropertyGroup>
 
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj" />
+  </ItemGroup>
+  
   <ItemGroup>
     <PackageReference Include="Appium.WebDriver" Version="4.3.1" />
     <PackageReference Include="Xunit.Extensions.Ordering" Version="1.4.5" />

+ 164 - 0
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<AppiumWebElement> _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<string?, ShowWindowMode, WindowStartupLocation> StartupLocationData()
+        {
+            var sizes = new[] { null, "400,300" };
+            var data = new TheoryData<string?, ShowWindowMode, WindowStartupLocation>();
+
+            foreach (var size in sizes)
+            {
+                foreach (var mode in Enum.GetValues<ShowWindowMode>())
+                {
+                    foreach (var location in Enum.GetValues<WindowStartupLocation>())
+                    {
+                        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
+        }
+    }
+}