From dcd3aa5e4f73d5ce2e92260349403bbec3d99a68 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 29 Jun 2022 15:49:23 +0200 Subject: [PATCH] Backport IntegrationTestApp to 0.10.x. Even though we don't have automation there and so can't do integration testing, it's still useful for manual testing. --- Avalonia.sln | 43 ++++-- samples/IntegrationTestApp/App.axaml | 7 + samples/IntegrationTestApp/App.axaml.cs | 24 ++++ .../IntegrationTestApp.csproj | 28 ++++ samples/IntegrationTestApp/MainWindow.axaml | 120 ++++++++++++++++ .../IntegrationTestApp/MainWindow.axaml.cs | 135 ++++++++++++++++++ samples/IntegrationTestApp/Program.cs | 22 +++ .../IntegrationTestApp/ShowWindowTest.axaml | 35 +++++ .../ShowWindowTest.axaml.cs | 40 ++++++ samples/IntegrationTestApp/bundle.sh | 5 + samples/IntegrationTestApp/nuget.config | 11 ++ src/Avalonia.Controls/ControlExtensions.cs | 23 +++ 12 files changed, 485 insertions(+), 8 deletions(-) create mode 100644 samples/IntegrationTestApp/App.axaml create mode 100644 samples/IntegrationTestApp/App.axaml.cs create mode 100644 samples/IntegrationTestApp/IntegrationTestApp.csproj create mode 100644 samples/IntegrationTestApp/MainWindow.axaml create mode 100644 samples/IntegrationTestApp/MainWindow.axaml.cs create mode 100644 samples/IntegrationTestApp/Program.cs create mode 100644 samples/IntegrationTestApp/ShowWindowTest.axaml create mode 100644 samples/IntegrationTestApp/ShowWindowTest.axaml.cs create mode 100644 samples/IntegrationTestApp/bundle.sh create mode 100644 samples/IntegrationTestApp/nuget.config diff --git a/Avalonia.sln b/Avalonia.sln index 7037a9721b..6c065a12e0 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -237,15 +237,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsInteropTest", "sampl EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControlSamples", "samples\SampleControls\ControlSamples.csproj", "{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTestApp", "samples\IntegrationTestApp\IntegrationTestApp.csproj", "{D3867680-B9C7-43D6-BF2C-697EC9CF1151}" +EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 - src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5 - src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 5 - src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5 - src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5 - src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Ad-Hoc|Any CPU = Ad-Hoc|Any CPU Ad-Hoc|iPhone = Ad-Hoc|iPhone @@ -2195,6 +2189,30 @@ Global {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|iPhone.Build.0 = Release|Any CPU {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|Any CPU.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|iPhone.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|iPhone.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|iPhone.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|Any CPU.Build.0 = Release|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|iPhone.ActiveCfg = Release|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|iPhone.Build.0 = Release|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {D3867680-B9C7-43D6-BF2C-697EC9CF1151}.Release|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -2258,8 +2276,17 @@ Global {C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098} {26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098} + {D3867680-B9C7-43D6-BF2C-697EC9CF1151} = {9B9E3891-2366-4253-A952-D08BCEB71098} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13 + src\Shared\RenderHelpers\RenderHelpers.projitems*{3e908f67-5543-4879-a1dc-08eace79b3cd}*SharedItemsImports = 5 + src\Shared\PlatformSupport\PlatformSupport.projitems*{7b92af71-6287-4693-9dcb-bd5b6e927e23}*SharedItemsImports = 5 + src\Shared\RenderHelpers\RenderHelpers.projitems*{7d2d3083-71dd-4cc9-8907-39a0d86fb322}*SharedItemsImports = 5 + src\Shared\PlatformSupport\PlatformSupport.projitems*{88060192-33d5-4932-b0f9-8bd2763e857d}*SharedItemsImports = 5 + src\Shared\PlatformSupport\PlatformSupport.projitems*{e4d9629c-f168-4224-3f51-a5e482ffbc42}*SharedItemsImports = 13 + EndGlobalSection EndGlobal diff --git a/samples/IntegrationTestApp/App.axaml b/samples/IntegrationTestApp/App.axaml new file mode 100644 index 0000000000..a833e096df --- /dev/null +++ b/samples/IntegrationTestApp/App.axaml @@ -0,0 +1,7 @@ + + + + + diff --git a/samples/IntegrationTestApp/App.axaml.cs b/samples/IntegrationTestApp/App.axaml.cs new file mode 100644 index 0000000000..022931366d --- /dev/null +++ b/samples/IntegrationTestApp/App.axaml.cs @@ -0,0 +1,24 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; + +namespace IntegrationTestApp +{ + public class App : Application + { + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow(); + } + + base.OnFrameworkInitializationCompleted(); + } + } +} diff --git a/samples/IntegrationTestApp/IntegrationTestApp.csproj b/samples/IntegrationTestApp/IntegrationTestApp.csproj new file mode 100644 index 0000000000..4284399357 --- /dev/null +++ b/samples/IntegrationTestApp/IntegrationTestApp.csproj @@ -0,0 +1,28 @@ + + + WinExe + net6.0 + enable + + + + IntegrationTestApp + net.avaloniaui.avalonia.integrationtestapp + true + 1.0.0 + + + + + + + + + + + + + + + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml new file mode 100644 index 0000000000..f4d6ad3ace --- /dev/null +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + TextBlockWithName + + TextBlockWithNameAndAutomationId + + Label for TextBox + + Foo + + + + + + + + + + + + + + + + Unchecked + Checked + ThreeState + + + + + + + Item 0 + Item 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + None + + + + + + + + + + + NonOwned + Owned + Modal + + + Manual + CenterScreen + CenterOwner + + + + + + + + + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs new file mode 100644 index 0000000000..9e180b12c5 --- /dev/null +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -0,0 +1,135 @@ +using System; +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; + +namespace IntegrationTestApp +{ + public class MainWindow : Window + { + public MainWindow() + { + InitializeComponent(); + InitializeViewMenu(); + this.AttachDevTools(); + AddHandler(Button.ClickEvent, OnButtonClick); + ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList(); + DataContext = this; + } + + public List ListBoxItems { get; } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + private void InitializeViewMenu() + { + var mainTabs = this.FindControl("MainTabs"); + var viewMenu = (NativeMenuItem)NativeMenu.GetMenu(this).Items[1]; + + foreach (TabItem tabItem in mainTabs.Items) + { + var menuItem = new NativeMenuItem + { + Header = (string)tabItem.Header!, + IsChecked = tabItem.IsSelected, + ToggleType = NativeMenuItemToggleType.Radio, + }; + + menuItem.Click += (s, e) => tabItem.IsSelected = true; + viewMenu.Menu.Items.Add(menuItem); + } + } + + 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 SendToBack() + { + var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!; + + foreach (var window in lifetime.Windows) + { + window.Activate(); + } + } + + private void RestoreAll() + { + var lifetime = (ClassicDesktopStyleApplicationLifetime)Application.Current!.ApplicationLifetime!; + + foreach (var window in lifetime.Windows) + { + if (window.WindowState == WindowState.Minimized) + window.WindowState = WindowState.Normal; + } + } + + private void MenuClicked(object? sender, RoutedEventArgs e) + { + var clickedMenuItemTextBlock = this.FindControl("ClickedMenuItem"); + clickedMenuItemTextBlock.Text = ((MenuItem)sender!).Header.ToString(); + } + + private void OnButtonClick(object? sender, RoutedEventArgs e) + { + var source = e.Source as Button; + + if (source?.Name == "ComboBoxSelectionClear") + this.FindControl("BasicComboBox").SelectedIndex = -1; + if (source?.Name == "ComboBoxSelectFirst") + this.FindControl("BasicComboBox").SelectedIndex = 0; + if (source?.Name == "ListBoxSelectionClear") + this.FindControl("BasicListBox").SelectedIndex = -1; + if (source?.Name == "MenuClickedMenuItemReset") + this.FindControl("ClickedMenuItem").Text = "None"; + if (source?.Name == "ShowWindow") + ShowWindow(); + if (source?.Name == "SendToBack") + SendToBack(); + if (source?.Name == "ExitFullscreen") + WindowState = WindowState.Normal; + if (source?.Name == "RestoreAll") + RestoreAll(); + } + } +} diff --git a/samples/IntegrationTestApp/Program.cs b/samples/IntegrationTestApp/Program.cs new file mode 100644 index 0000000000..c09b249cfa --- /dev/null +++ b/samples/IntegrationTestApp/Program.cs @@ -0,0 +1,22 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; + +namespace IntegrationTestApp +{ + class Program + { + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() + .LogToTrace(); + } +} diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml b/samples/IntegrationTestApp/ShowWindowTest.axaml new file mode 100644 index 0000000000..40c1642e91 --- /dev/null +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + Normal + Minimized + Maximized + Fullscreen + + + diff --git a/samples/IntegrationTestApp/ShowWindowTest.axaml.cs b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs new file mode 100644 index 0000000000..001f186761 --- /dev/null +++ b/samples/IntegrationTestApp/ShowWindowTest.axaml.cs @@ -0,0 +1,40 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Markup.Xaml; +using Avalonia.Rendering; + +namespace IntegrationTestApp +{ + public class ShowWindowTest : Window + { + public ShowWindowTest() + { + InitializeComponent(); + DataContext = this; + PositionChanged += (s, e) => this.GetControl("Position").Text = $"{Position}"; + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } + + protected override void OnOpened(EventArgs e) + { + base.OnOpened(e); + var scaling = PlatformImpl!.DesktopScaling; + this.GetControl("Position").Text = $"{Position}"; + this.GetControl("ScreenRect").Text = $"{Screens.ScreenFromVisual(this)?.WorkingArea}"; + this.GetControl("Scaling").Text = $"{scaling}"; + + if (Owner is not null) + { + var ownerRect = this.GetControl("OwnerRect"); + var owner = (Window)Owner; + ownerRect.Text = $"{owner.Position}, {PixelSize.FromSize(owner.FrameSize!.Value, scaling)}"; + } + } + } +} diff --git a/samples/IntegrationTestApp/bundle.sh b/samples/IntegrationTestApp/bundle.sh new file mode 100644 index 0000000000..505991582e --- /dev/null +++ b/samples/IntegrationTestApp/bundle.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +cd $(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd) +dotnet restore -r osx-arm64 +dotnet msbuild -t:BundleApp -p:RuntimeIdentifier=osx-arm64 -p:_AvaloniaUseExternalMSBuild=false \ No newline at end of file diff --git a/samples/IntegrationTestApp/nuget.config b/samples/IntegrationTestApp/nuget.config new file mode 100644 index 0000000000..6c273ab3d9 --- /dev/null +++ b/samples/IntegrationTestApp/nuget.config @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/src/Avalonia.Controls/ControlExtensions.cs b/src/Avalonia.Controls/ControlExtensions.cs index c3cab0a729..86ad335e26 100644 --- a/src/Avalonia.Controls/ControlExtensions.cs +++ b/src/Avalonia.Controls/ControlExtensions.cs @@ -66,6 +66,29 @@ namespace Avalonia.Controls return nameScope.Find(name); } + /// + /// Finds the named control in the scope of the specified control and throws if not found. + /// + /// The type of the control to find. + /// The control to look in. + /// The name of the control to find. + /// The control. + public static T GetControl(this IControl control, string name) where T : class, IControl + { + _ = control ?? throw new ArgumentNullException(nameof(control)); + _ = name ?? throw new ArgumentNullException(nameof(name)); + + var nameScope = control.FindNameScope(); + + if (nameScope == null) + { + throw new InvalidOperationException("Could not find parent name scope."); + } + + return nameScope.Find(name) ?? + throw new ArgumentException($"Could not find control named '{name}'."); + } + /// /// Sets a pseudoclass depending on an observable trigger. ///