diff --git a/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs index 5ecfb29afc..9fa4ede3ca 100644 --- a/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs @@ -1,17 +1,42 @@ -using Avalonia.Controls; +using System; +using Avalonia.Automation.Provider; +using Avalonia.Controls; using Avalonia.Controls.Primitives; namespace Avalonia.Automation.Peers { - public class MenuItemAutomationPeer : ControlAutomationPeer + public class MenuItemAutomationPeer : ControlAutomationPeer, IToggleProvider { public MenuItemAutomationPeer(MenuItem owner) - : base(owner) - { + : base(owner) + { + owner.PropertyChanged += OwnerPropertyChanged; } public new MenuItem Owner => (MenuItem)base.Owner; + ToggleState IToggleProvider.ToggleState + => Owner.IsChecked ? ToggleState.On : ToggleState.Off; + + void IToggleProvider.Toggle() + { + EnsureEnabled(); + + if (Owner.HasSubMenu) + return; + + switch (Owner.ToggleType) + { + case MenuItemToggleType.CheckBox: + Owner.SetCurrentValue(MenuItem.IsCheckedProperty, !Owner.IsChecked); + break; + case MenuItemToggleType.Radio: + if (!Owner.IsChecked) + Owner.SetCurrentValue(MenuItem.IsCheckedProperty, true); + break; + } + } + protected override string? GetAccessKeyCore() { var result = base.GetAccessKeyCore(); @@ -55,5 +80,27 @@ namespace Avalonia.Automation.Peers return result; } + + protected override object? GetProviderCore(Type providerType) + { + if (providerType == typeof(IToggleProvider) && Owner.ToggleType == MenuItemToggleType.None) + return null; + + return base.GetProviderCore(providerType); + } + + private void OwnerPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + { + if (e.Property == MenuItem.IsCheckedProperty && Owner.ToggleType != MenuItemToggleType.None) + { + RaisePropertyChangedEvent( + TogglePatternIdentifiers.ToggleStateProperty, + ToState(e.GetOldValue()), + ToState(e.GetNewValue())); + } + } + + private static ToggleState ToState(bool value) + => value ? ToggleState.On : ToggleState.Off; } } diff --git a/tests/Avalonia.Controls.UnitTests/Automation/MenuItemAutomationPeerTests.cs b/tests/Avalonia.Controls.UnitTests/Automation/MenuItemAutomationPeerTests.cs new file mode 100644 index 0000000000..0ef10c9a01 --- /dev/null +++ b/tests/Avalonia.Controls.UnitTests/Automation/MenuItemAutomationPeerTests.cs @@ -0,0 +1,95 @@ +using Avalonia.Automation; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Provider; +using Avalonia.UnitTests; +using Xunit; + +namespace Avalonia.Controls.UnitTests.Automation; + +public class MenuItemAutomationPeerTests : ScopedTestBase +{ + private static IToggleProvider GetProvider(MenuItem menuItem) + { + var provider = ControlAutomationPeer.CreatePeerForElement(menuItem).GetProvider(); + Assert.NotNull(provider); + return provider; + } + + [Fact] + public void Toggle_Provider_Is_Not_Exposed_When_ToggleType_None() + { + var peer = ControlAutomationPeer.CreatePeerForElement(new MenuItem()); + + Assert.Null(peer.GetProvider()); + } + + [Theory] + [InlineData(MenuItemToggleType.CheckBox)] + [InlineData(MenuItemToggleType.Radio)] + public void Toggle_Provider_Is_Exposed_For_Checkable_Items(MenuItemToggleType toggleType) + { + var peer = ControlAutomationPeer.CreatePeerForElement(new MenuItem { ToggleType = toggleType }); + + Assert.NotNull(peer.GetProvider()); + } + + [Fact] + public void ToggleState_Reflects_IsChecked() + { + var menuItem = new MenuItem { ToggleType = MenuItemToggleType.CheckBox }; + var provider = GetProvider(menuItem); + + Assert.Equal(ToggleState.Off, provider.ToggleState); + menuItem.IsChecked = true; + Assert.Equal(ToggleState.On, provider.ToggleState); + } + + [Fact] + public void Toggle_Flips_CheckBox_Item() + { + var menuItem = new MenuItem { ToggleType = MenuItemToggleType.CheckBox }; + var provider = GetProvider(menuItem); + + provider.Toggle(); + Assert.True(menuItem.IsChecked); + + provider.Toggle(); + Assert.False(menuItem.IsChecked); + } + + [Fact] + public void Toggle_Checks_But_Does_Not_Uncheck_Radio_Item() + { + var menuItem = new MenuItem { ToggleType = MenuItemToggleType.Radio }; + var provider = GetProvider(menuItem); + + provider.Toggle(); + Assert.True(menuItem.IsChecked); + + provider.Toggle(); + Assert.True(menuItem.IsChecked); + } + + [Fact] + public void Toggle_Raises_ToggleState_PropertyChanged() + { + var menuItem = new MenuItem { ToggleType = MenuItemToggleType.CheckBox }; + var peer = ControlAutomationPeer.CreatePeerForElement(menuItem); + var provider = GetProvider(menuItem); + + var raised = 0; + peer.PropertyChanged += (_, e) => + { + if (e.Property == TogglePatternIdentifiers.ToggleStateProperty) + { + Assert.Equal(ToggleState.Off, e.OldValue); + Assert.Equal(ToggleState.On, e.NewValue); + raised++; + } + }; + + provider.Toggle(); + + Assert.Equal(1, raised); + } +}