Browse Source

[macOS] Add NativeDock.Menu API for adding menu items to macOS dock icon (#20634)

* Native DockMenu code

* Add Native Interop

* Update ControlCatalog sample

* Add unit tests

* Add Action<IAvnMenu> to AvaloniaNativeMenuExporter

* Add dynamic dock item demo

* Move s_dockMenu reference to App

* Use Appium tests

* Revert INativeMenuExporterResetHandler

* Properly set the button for the checkbox

* Add dock test

* I hate Appium

* Rename NativeMenu.DockMenu to NativeDock.Menu

* Make static

* Remove Dock Click Test

* Add white space back for cleaner diff

* Reduce MenuExporter back to one

* Revert UpdateIfNeeded to private

* Revert QueueReset to private too... and fix some whitespace

* Revert IAvnMenuItem/IAvnMenu back

* That's what I get not comparing it to master

* And update this too

* Add documentation
pull/20719/head
Tim Miller 1 month ago
committed by GitHub
parent
commit
ef70264236
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 12
      native/Avalonia.Native/src/OSX/app.mm
  2. 1
      native/Avalonia.Native/src/OSX/common.h
  3. 11
      native/Avalonia.Native/src/OSX/main.mm
  4. 9
      samples/ControlCatalog/App.xaml
  5. 34
      samples/ControlCatalog/App.xaml.cs
  6. 7
      samples/IntegrationTestApp/App.axaml
  7. 25
      samples/IntegrationTestApp/App.axaml.cs
  8. 6
      samples/IntegrationTestApp/Pages/DesktopPage.axaml
  9. 10
      samples/IntegrationTestApp/Pages/DesktopPage.axaml.cs
  10. 28
      src/Avalonia.Controls/NativeDock.cs
  11. 117
      src/Avalonia.Native/AvaloniaNativeMenuExporter.cs
  12. 5
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  13. 1
      src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs
  14. 1
      src/Avalonia.Native/avn.idl
  15. 37
      tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs

12
native/Avalonia.Native/src/OSX/app.mm

@ -1,11 +1,13 @@
#include "common.h" #include "common.h"
#include "AvnString.h" #include "AvnString.h"
#include "menu.h"
@interface AvnAppDelegate : NSObject<NSApplicationDelegate> @interface AvnAppDelegate : NSObject<NSApplicationDelegate>
-(AvnAppDelegate* _Nonnull) initWithEvents: (IAvnApplicationEvents* _Nonnull) events; -(AvnAppDelegate* _Nonnull) initWithEvents: (IAvnApplicationEvents* _Nonnull) events;
-(void) releaseEvents; -(void) releaseEvents;
@end @end
NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular; NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular;
static NSMenu* s_dockMenu = nil;
@implementation AvnAppDelegate @implementation AvnAppDelegate
ComPtr<IAvnApplicationEvents> _events; ComPtr<IAvnApplicationEvents> _events;
@ -86,6 +88,11 @@ ComPtr<IAvnApplicationEvents> _events;
return _events->TryShutdown() ? NSTerminateNow : NSTerminateCancel; return _events->TryShutdown() ? NSTerminateNow : NSTerminateCancel;
} }
- (NSMenu *)applicationDockMenu:(NSApplication *)sender
{
return s_dockMenu;
}
@end @end
@interface AvnApplication : NSApplication @interface AvnApplication : NSApplication
@ -180,3 +187,8 @@ extern IAvnApplicationCommands* CreateApplicationCommands()
{ {
return new AvnApplicationCommands(); return new AvnApplicationCommands();
} }
extern void SetDockMenu(NSMenu* menu)
{
s_dockMenu = menu;
}

1
native/Avalonia.Native/src/OSX/common.h

@ -38,6 +38,7 @@ extern void SetAppMenu(IAvnMenu *menu);
extern void SetServicesMenu (IAvnMenu* menu); extern void SetServicesMenu (IAvnMenu* menu);
extern IAvnMenu* GetAppMenu (); extern IAvnMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem (); extern NSMenuItem* GetAppMenuItem ();
extern void SetDockMenu(NSMenu* menu);
extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDelegate); extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDelegate);
extern void ReleaseAvnAppEvents(); extern void ReleaseAvnAppEvents();

11
native/Avalonia.Native/src/OSX/main.mm

@ -1,6 +1,7 @@
//This file will contain actual IID structures //This file will contain actual IID structures
#define COM_GUIDS_MATERIALIZE #define COM_GUIDS_MATERIALIZE
#include "common.h" #include "common.h"
#include "menu.h"
static NSString* s_appTitle = @"Avalonia"; static NSString* s_appTitle = @"Avalonia";
static int disableSetProcessName = 0; static int disableSetProcessName = 0;
@ -481,7 +482,17 @@ public:
return S_OK; return S_OK;
} }
virtual HRESULT SetDockMenu(IAvnMenu* dockMenu) override
{
START_COM_CALL;
@autoreleasepool
{
auto nativeMenu = dynamic_cast<AvnAppMenu*>(dockMenu);
::SetDockMenu(nativeMenu != nullptr ? nativeMenu->GetNative() : nil);
return S_OK;
}
}
}; };

9
samples/ControlCatalog/App.xaml

@ -59,6 +59,15 @@
<Setter Property="FontSize" Value="12" /> <Setter Property="FontSize" Value="12" />
</Style> </Style>
</Application.Styles> </Application.Styles>
<NativeDock.Menu>
<NativeMenu>
<NativeMenuItem Header="New Window" Click="OnDockNewWindowClicked"/>
<NativeMenuItemSeparator/>
<NativeMenuItem Header="Show Main Window" Click="OnDockShowMainWindowClicked"/>
<NativeMenuItemSeparator/>
<NativeMenuItem Header="Add Dock Menu Item" Click="OnDockAddItemClicked"/>
</NativeMenu>
</NativeDock.Menu>
<TrayIcon.Icons> <TrayIcon.Icons>
<TrayIcons> <TrayIcons>
<TrayIcon Icon="/Assets/test_icon.ico" MacOSProperties.IsTemplateIcon="true" ToolTipText="Avalonia Tray Icon ToolTip"> <TrayIcon Icon="/Assets/test_icon.ico" MacOSProperties.IsTemplateIcon="true" ToolTipText="Avalonia Tray Icon ToolTip">

34
samples/ControlCatalog/App.xaml.cs

@ -64,6 +64,40 @@ namespace ControlCatalog
base.OnFrameworkInitializationCompleted(); base.OnFrameworkInitializationCompleted();
} }
public void OnDockNewWindowClicked(object? sender, EventArgs e)
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime)
{
var window = new MainWindow();
window.Show();
}
}
public void OnDockShowMainWindowClicked(object? sender, EventArgs e)
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
{
desktopLifetime.MainWindow?.Activate();
}
}
private int _dockMenuItemCount;
public void OnDockAddItemClicked(object? sender, EventArgs e)
{
var dockMenu = NativeDock.GetMenu(this);
if (dockMenu is not null)
{
_dockMenuItemCount++;
var item = new NativeMenuItem($"New item {_dockMenuItemCount}");
item.Click += (_, _) =>
{
dockMenu.Items.Remove(item);
};
dockMenu.Items.Insert(0, item);
}
}
private CatalogTheme _prevTheme; private CatalogTheme _prevTheme;
public static CatalogTheme CurrentTheme => ((App)Current!)._prevTheme; public static CatalogTheme CurrentTheme => ((App)Current!)._prevTheme;
public static void SetCatalogThemes(CatalogTheme theme) public static void SetCatalogThemes(CatalogTheme theme)

7
samples/IntegrationTestApp/App.axaml

@ -7,6 +7,13 @@
<Application.Styles> <Application.Styles>
<FluentTheme /> <FluentTheme />
</Application.Styles> </Application.Styles>
<NativeDock.Menu>
<NativeMenu>
<NativeMenuItem Header="Show Main Window"
Command="{Binding DockMenuCommand}"
CommandParameter="DockMenuShowMainWindow"/>
</NativeMenu>
</NativeDock.Menu>
<TrayIcon.Icons> <TrayIcon.Icons>
<TrayIcons> <TrayIcons>
<TrayIcon Icon="/Assets/icon.ico" <TrayIcon Icon="/Assets/icon.ico"

25
samples/IntegrationTestApp/App.axaml.cs

@ -1,8 +1,10 @@
using System.Linq;
using System.Windows.Input; using System.Windows.Input;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using MiniMvvm; using MiniMvvm;
@ -18,6 +20,13 @@ namespace IntegrationTestApp
{ {
_mainWindow!.Get<CheckBox>(name).IsChecked = true; _mainWindow!.Get<CheckBox>(name).IsChecked = true;
}); });
DockMenuCommand = MiniCommand.Create<string>(name =>
{
// This is for the "Show Main Window" dock menu item in the test.
// It doesn't actually show the main window, but sets the checkbox to true in the page.
var checkbox = _mainWindow!.GetLogicalDescendants().OfType<CheckBox>().FirstOrDefault(x => x.Name == name);
if (checkbox != null) checkbox.IsChecked = true;
});
DataContext = this; DataContext = this;
} }
@ -37,5 +46,21 @@ namespace IntegrationTestApp
} }
public ICommand TrayIconCommand { get; } public ICommand TrayIconCommand { get; }
public ICommand DockMenuCommand { get; }
public void AddDockMenuItem(string header)
{
var dockMenu = NativeDock.GetMenu(this);
if (dockMenu is not null)
{
dockMenu.Items.Insert(0, new NativeMenuItem(header));
}
}
public int GetDockMenuItemCount()
{
var dockMenu = NativeDock.GetMenu(this);
return dockMenu?.Items.Count ?? 0;
}
} }
} }

6
samples/IntegrationTestApp/Pages/DesktopPage.axaml

@ -10,5 +10,11 @@
<Button Name="ToggleTrayIconVisible" <Button Name="ToggleTrayIconVisible"
Content="Toggle TrayIcon Visible" Content="Toggle TrayIcon Visible"
Click="ToggleTrayIconVisible_Click"/> Click="ToggleTrayIconVisible_Click"/>
<Separator Margin="0,8"/>
<CheckBox x:FieldModifier="public" Name="DockMenuShowMainWindow">Dock Menu Show Main Window Clicked</CheckBox>
<Button Name="AddDockMenuItem"
Content="Add Dock Menu Item"
Click="AddDockMenuItem_Click"/>
<TextBlock Name="DockMenuItemCount" Text="0"/>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

10
samples/IntegrationTestApp/Pages/DesktopPage.axaml.cs

@ -6,6 +6,8 @@ namespace IntegrationTestApp.Pages;
public partial class DesktopPage : UserControl public partial class DesktopPage : UserControl
{ {
private int _dockMenuItemCount;
public DesktopPage() public DesktopPage()
{ {
InitializeComponent(); InitializeComponent();
@ -16,4 +18,12 @@ public partial class DesktopPage : UserControl
var icon = TrayIcon.GetIcons(Application.Current!)!.FirstOrDefault()!; var icon = TrayIcon.GetIcons(Application.Current!)!.FirstOrDefault()!;
icon.IsVisible = !icon.IsVisible; icon.IsVisible = !icon.IsVisible;
} }
private void AddDockMenuItem_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
var app = (App)Application.Current!;
_dockMenuItemCount++;
app.AddDockMenuItem($"Dynamic Item {_dockMenuItemCount}");
DockMenuItemCount.Text = app.GetDockMenuItemCount().ToString();
}
} }

28
src/Avalonia.Controls/NativeDock.cs

@ -0,0 +1,28 @@
namespace Avalonia.Controls
{
/// <summary>
/// Allows native menu support on platforms where a <see cref="NativeMenu"/> can be attached to the dock.
/// </summary>
public static class NativeDock
{
/// <summary>
/// Defines the Menu attached property.
/// </summary>
public static readonly AttachedProperty<NativeMenu?> MenuProperty =
AvaloniaProperty.RegisterAttached<AvaloniaObject, NativeMenu?>("Menu", typeof(NativeDock));
/// <summary>
/// Sets the value of the attached <see cref="MenuProperty"/>.
/// </summary>
/// <param name="o">The control to set the menu for.</param>
/// <param name="menu">The menu to set.</param>
public static void SetMenu(AvaloniaObject o, NativeMenu? menu) => o.SetValue(MenuProperty, menu);
/// <summary>
/// Gets the value of the attached <see cref="MenuProperty"/>.
/// </summary>
/// <param name="o">The control to get the menu for.</param>
/// <returns>The menu of the control.</returns>
public static NativeMenu? GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty);
}
}

117
src/Avalonia.Native/AvaloniaNativeMenuExporter.cs

@ -1,4 +1,4 @@
using System; using System;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
@ -6,24 +6,29 @@ using Avalonia.Dialogs;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Native.Interop; using Avalonia.Native.Interop;
using Avalonia.Native.Interop.Impl; using Avalonia.Native.Interop.Impl;
using Avalonia.Reactive;
using Avalonia.Threading; using Avalonia.Threading;
namespace Avalonia.Native namespace Avalonia.Native
{ {
internal class AvaloniaNativeMenuExporter : ITopLevelNativeMenuExporter internal class AvaloniaNativeMenuExporter : ITopLevelNativeMenuExporter
{ {
internal enum MenuTarget { Application, Window, TrayIcon, Dock }
private readonly IAvaloniaNativeFactory _factory; private readonly IAvaloniaNativeFactory _factory;
private readonly MenuTarget _target;
private bool _resetQueued = true; private bool _resetQueued = true;
private bool _exported; private bool _exported;
private readonly IAvnWindow? _nativeWindow; private readonly IAvnWindow? _nativeWindow;
private NativeMenu? _menu; private NativeMenu? _menu;
private __MicroComIAvnMenuProxy? _nativeMenu; private __MicroComIAvnMenuProxy? _nativeMenu;
private readonly IAvnTrayIcon? _trayIcon; private readonly IAvnTrayIcon? _trayIcon;
private readonly IAvnApplicationCommands _applicationCommands; private readonly IAvnApplicationCommands? _applicationCommands;
public AvaloniaNativeMenuExporter(IAvnWindow nativeWindow, IAvaloniaNativeFactory factory) public AvaloniaNativeMenuExporter(IAvnWindow nativeWindow, IAvaloniaNativeFactory factory)
{ {
_factory = factory; _factory = factory;
_target = MenuTarget.Window;
_nativeWindow = nativeWindow; _nativeWindow = nativeWindow;
_applicationCommands = _factory.CreateApplicationCommands(); _applicationCommands = _factory.CreateApplicationCommands();
@ -33,6 +38,7 @@ namespace Avalonia.Native
public AvaloniaNativeMenuExporter(IAvaloniaNativeFactory factory) public AvaloniaNativeMenuExporter(IAvaloniaNativeFactory factory)
{ {
_factory = factory; _factory = factory;
_target = MenuTarget.Application;
_applicationCommands = _factory.CreateApplicationCommands(); _applicationCommands = _factory.CreateApplicationCommands();
DoLayoutReset(); DoLayoutReset();
@ -41,21 +47,66 @@ namespace Avalonia.Native
public AvaloniaNativeMenuExporter(IAvnTrayIcon trayIcon, IAvaloniaNativeFactory factory) public AvaloniaNativeMenuExporter(IAvnTrayIcon trayIcon, IAvaloniaNativeFactory factory)
{ {
_factory = factory; _factory = factory;
_target = MenuTarget.TrayIcon;
_trayIcon = trayIcon; _trayIcon = trayIcon;
_applicationCommands = _factory.CreateApplicationCommands(); _applicationCommands = _factory.CreateApplicationCommands();
DoLayoutReset(); DoLayoutReset();
} }
internal AvaloniaNativeMenuExporter(IAvaloniaNativeFactory factory, MenuTarget target)
{
_factory = factory;
_target = target;
_resetQueued = false;
var macOpts = AvaloniaLocator.Current.GetService<MacOSPlatformOptions>() ?? new MacOSPlatformOptions();
if (macOpts.DisableNativeMenus)
{
return;
}
NativeDock.MenuProperty.Changed.Subscribe(args =>
{
if (args.Sender is Application)
{
SetNativeMenu(args.NewValue.GetValueOrDefault());
}
});
var app = Application.Current;
if (app is not null)
{
var dockMenu = NativeDock.GetMenu(app);
if (dockMenu is not null)
{
SetNativeMenu(dockMenu);
}
}
}
public bool IsNativeMenuExported => _exported; public bool IsNativeMenuExported => _exported;
public event EventHandler OnIsNativeMenuExportedChanged { add { } remove { } } public event EventHandler OnIsNativeMenuExportedChanged { add { } remove { } }
public void SetNativeMenu(NativeMenu? menu) public void SetNativeMenu(NativeMenu? menu)
{
if (_target == MenuTarget.Dock)
{
_menu = menu;
if (_menu is not null)
{
DoLayoutReset(true);
}
}
else
{ {
_menu = menu ?? new NativeMenu(); _menu = menu ?? new NativeMenu();
DoLayoutReset(true); DoLayoutReset(true);
} }
}
internal void UpdateIfNeeded() internal void UpdateIfNeeded()
{ {
@ -109,7 +160,7 @@ namespace Avalonia.Native
hideItem.Click += (_, _) => hideItem.Click += (_, _) =>
{ {
_applicationCommands.HideApp(); _applicationCommands?.HideApp();
}; };
appMenu.Add(hideItem); appMenu.Add(hideItem);
@ -120,14 +171,14 @@ namespace Avalonia.Native
}; };
hideOthersItem.Click += (_, _) => hideOthersItem.Click += (_, _) =>
{ {
_applicationCommands.HideOthers(); _applicationCommands?.HideOthers();
}; };
appMenu.Add(hideOthersItem); appMenu.Add(hideOthersItem);
var showAllItem = new NativeMenuItem("Show All"); var showAllItem = new NativeMenuItem("Show All");
showAllItem.Click += (_, _) => showAllItem.Click += (_, _) =>
{ {
_applicationCommands.ShowAll(); _applicationCommands?.ShowAll();
}; };
appMenu.Add(showAllItem); appMenu.Add(showAllItem);
@ -165,9 +216,9 @@ namespace Avalonia.Native
{ {
_resetQueued = false; _resetQueued = false;
if (_nativeWindow is null) switch (_target)
{ {
if (_trayIcon is null) case MenuTarget.Application:
{ {
var app = Application.Current; var app = Application.Current;
var appMenu = app is null ? null : NativeMenu.GetMenu(app); var appMenu = app is null ? null : NativeMenu.GetMenu(app);
@ -181,17 +232,34 @@ namespace Avalonia.Native
} }
SetMenu(appMenu); SetMenu(appMenu);
break;
} }
else if (_menu != null)
case MenuTarget.Window:
{
if (_menu != null)
{
SetMenu(_nativeWindow, _menu);
}
break;
}
case MenuTarget.TrayIcon:
{
if (_menu != null)
{ {
SetMenu(_trayIcon, _menu); SetMenu(_trayIcon, _menu);
} }
break;
} }
else
case MenuTarget.Dock:
{ {
if (_menu != null) if (_menu != null)
{ {
SetMenu(_nativeWindow, _menu); SetDockMenu(_menu);
}
break;
} }
} }
@ -253,7 +321,28 @@ namespace Avalonia.Native
} }
} }
private void SetMenu(IAvnWindow avnWindow, NativeMenu menu) private void SetMenu(IAvnWindow? avnWindow, NativeMenu menu)
{
var setMenu = false;
if (_nativeMenu is null)
{
_nativeMenu = __MicroComIAvnMenuProxy.Create(_factory);
_nativeMenu.Initialize(this, menu, "");
setMenu = true;
}
_nativeMenu.Update(_factory, menu);
if(setMenu)
{
avnWindow?.SetMainMenu(_nativeMenu);
}
}
private void SetMenu(IAvnTrayIcon? trayIcon, NativeMenu menu)
{ {
var setMenu = false; var setMenu = false;
@ -270,11 +359,11 @@ namespace Avalonia.Native
if(setMenu) if(setMenu)
{ {
avnWindow.SetMainMenu(_nativeMenu); trayIcon?.SetMenu(_nativeMenu);
} }
} }
private void SetMenu(IAvnTrayIcon trayIcon, NativeMenu menu) private void SetDockMenu(NativeMenu menu)
{ {
var setMenu = false; var setMenu = false;
@ -291,7 +380,7 @@ namespace Avalonia.Native
if (setMenu) if (setMenu)
{ {
trayIcon.SetMenu(_nativeMenu); _factory.SetDockMenu(_nativeMenu);
} }
} }
} }

5
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -66,6 +66,11 @@ namespace Avalonia.Native
var exporter = new AvaloniaNativeMenuExporter(_factory); var exporter = new AvaloniaNativeMenuExporter(_factory);
} }
public void SetupApplicationDockMenuExporter()
{
_ = new AvaloniaNativeMenuExporter(_factory, AvaloniaNativeMenuExporter.MenuTarget.Dock);
}
public void SetupApplicationName() public void SetupApplicationName()
{ {
if (!string.IsNullOrWhiteSpace(Application.Current!.Name)) if (!string.IsNullOrWhiteSpace(Application.Current!.Name))

1
src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs

@ -22,6 +22,7 @@ namespace Avalonia
{ {
platform.SetupApplicationName(); platform.SetupApplicationName();
platform.SetupApplicationMenuExporter(); platform.SetupApplicationMenuExporter();
platform.SetupApplicationDockMenuExporter();
}); });
}); });

1
src/Avalonia.Native/avn.idl

@ -724,6 +724,7 @@ interface IAvaloniaNativeFactory : IUnknown
HRESULT CreatePlatformRenderTimer(IAvnPlatformRenderTimer** ppv); HRESULT CreatePlatformRenderTimer(IAvnPlatformRenderTimer** ppv);
HRESULT ImportMTLSharedEvent([intptr]void* idMtlSharedEvent, IAvnMTLSharedEvent** ppv); HRESULT ImportMTLSharedEvent([intptr]void* idMtlSharedEvent, IAvnMTLSharedEvent** ppv);
HRESULT CreateMemoryManagementHelper(IAvnNativeObjectsMemoryManagement** ppv); HRESULT CreateMemoryManagementHelper(IAvnNativeObjectsMemoryManagement** ppv);
HRESULT SetDockMenu(IAvnMenu* menu);
} }
[uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)] [uuid(233e094f-9b9f-44a3-9a6e-6948bbdd9fb1)]

37
tests/Avalonia.IntegrationTests.Appium/NativeMenuTests.cs

@ -1,4 +1,6 @@
using System.Threading; using System.Diagnostics;
using System.IO;
using System.Threading;
using Xunit; using Xunit;
namespace Avalonia.IntegrationTests.Appium namespace Avalonia.IntegrationTests.Appium
@ -86,4 +88,37 @@ namespace Avalonia.IntegrationTests.Appium
Assert.Contains(toolTipCandidates, x => x.Text == "Tip:Button"); Assert.Contains(toolTipCandidates, x => x.Text == "Tip:Button");
} }
} }
[Collection("Default")]
public class DockMenuTests : TestBase
{
private const string DockAppName = "IntegrationTestApp";
public DockMenuTests(DefaultAppFixture fixture)
: base(fixture, "DesktopPage")
{
}
[PlatformFact(TestPlatforms.MacOS)]
public void MacOS_DockMenu_Can_Add_Items_Dynamically()
{
var countText = Session.FindElementByAccessibilityId("DockMenuItemCount");
Assert.Equal("0", countText.Text);
var addButton = Session.FindElementByAccessibilityId("AddDockMenuItem");
addButton.Click();
Thread.Sleep(500);
countText = Session.FindElementByAccessibilityId("DockMenuItemCount");
Assert.Equal("2", countText.Text);
addButton.Click();
Thread.Sleep(500);
countText = Session.FindElementByAccessibilityId("DockMenuItemCount");
Assert.Equal("3", countText.Text);
}
}
} }

Loading…
Cancel
Save