diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 353e01dca70..58e8aef1e97 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -56,6 +56,16 @@ + + + Sample RadioButton + + Three States: Option 1 + Three States: Option 2 + + + + Unchecked diff --git a/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs new file mode 100644 index 00000000000..b0f83c1f2ad --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs @@ -0,0 +1,63 @@ +using System; +using Avalonia.Automation; +using Avalonia.Automation.Peers; +using Avalonia.Automation.Provider; + +namespace Avalonia.Controls.Automation.Peers +{ + public class RadioButtonAutomationPeer : ToggleButtonAutomationPeer, ISelectionItemProvider + { + public RadioButtonAutomationPeer(RadioButton owner) : base(owner) + { + owner.PropertyChanged += (a, e) => + { + if (e.Property == RadioButton.IsCheckedProperty) + { + RaiseToggleStatePropertyChangedEvent((bool?)e.OldValue, (bool?)e.NewValue); + } + }; + } + + override protected string GetClassNameCore() + { + return "RadioButton"; + } + + override protected AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.RadioButton; + } + + public bool IsSelected => ((RadioButton)Owner).IsChecked == true; + + public ISelectionProvider? SelectionContainer => null; + + public void AddToSelection() + { + if (((RadioButton)Owner).IsChecked != true) + throw new InvalidOperationException("Operation cannot be performed"); + } + + public void RemoveFromSelection() + { + if (((RadioButton)Owner).IsChecked == true) + throw new InvalidOperationException("Operation cannot be performed"); + } + + public void Select() + { + if (!IsEnabled()) + throw new InvalidOperationException("Element is disabled thus it cannot be selected"); + + ((RadioButton)Owner).IsChecked = true; + } + + internal virtual void RaiseToggleStatePropertyChangedEvent(bool? oldValue, bool? newValue) + { + RaisePropertyChangedEvent( + SelectionItemPatternIdentifiers.IsSelectedProperty, + oldValue == true, + newValue == true); + } + } +} diff --git a/src/Avalonia.Controls/Automation/SelectionItemPatternIdentifiers.cs b/src/Avalonia.Controls/Automation/SelectionItemPatternIdentifiers.cs new file mode 100644 index 00000000000..418ae1f1fe3 --- /dev/null +++ b/src/Avalonia.Controls/Automation/SelectionItemPatternIdentifiers.cs @@ -0,0 +1,16 @@ +using Avalonia.Automation.Provider; + +namespace Avalonia.Automation +{ + /// + /// Contains values used as identifiers by . + /// + public static class SelectionItemPatternIdentifiers + { + /// Indicates the element is currently selected. + public static AutomationProperty IsSelectedProperty { get; } = new AutomationProperty(); + + /// Indicates the element is currently selected. + public static AutomationProperty SelectionContainerProperty { get; } = new AutomationProperty(); + } +} diff --git a/src/Avalonia.Controls/RadioButton.cs b/src/Avalonia.Controls/RadioButton.cs index b87be34a9d0..87772aced70 100644 --- a/src/Avalonia.Controls/RadioButton.cs +++ b/src/Avalonia.Controls/RadioButton.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using Avalonia.Automation.Peers; +using Avalonia.Controls.Automation.Peers; using Avalonia.Controls.Primitives; using Avalonia.Reactive; using Avalonia.Rendering; @@ -147,6 +149,11 @@ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e } } + protected override AutomationPeer OnCreateAutomationPeer() + { + return new RadioButtonAutomationPeer(this); + } + private void SetGroupName(string? newGroupName) { var oldGroupName = GroupName; diff --git a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs index 48ebd4068e8..3eeedc4b5da 100644 --- a/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs +++ b/src/Windows/Avalonia.Win32/Automation/AutomationNode.cs @@ -41,6 +41,8 @@ internal partial class AutomationNode : MarshalByRefObject, { SelectionPatternIdentifiers.CanSelectMultipleProperty, UiaPropertyId.SelectionCanSelectMultiple }, { SelectionPatternIdentifiers.IsSelectionRequiredProperty, UiaPropertyId.SelectionIsSelectionRequired }, { SelectionPatternIdentifiers.SelectionProperty, UiaPropertyId.SelectionSelection }, + { SelectionItemPatternIdentifiers.IsSelectedProperty, UiaPropertyId.SelectionItemIsSelected }, + { SelectionItemPatternIdentifiers.SelectionContainerProperty, UiaPropertyId.SelectionItemSelectionContainer } }; private static ConditionalWeakTable s_nodes = new(); diff --git a/tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.cs b/tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.cs new file mode 100644 index 00000000000..5bd0a051554 --- /dev/null +++ b/tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using OpenQA.Selenium.Appium; +using Xunit; + +namespace Avalonia.IntegrationTests.Appium +{ + [Collection("Default")] + public class RadioButtonTests + { + private readonly AppiumDriver _session; + + public RadioButtonTests(TestAppFixture fixture) + { + _session = fixture.Session; + + var tabs = _session.FindElementByAccessibilityId("MainTabs"); + tabs.FindElementByName("RadioButton").Click(); + } + + + [Fact] + public void RadioButton_IsChecked_True_When_Clicked() + { + var button = _session.FindElementByAccessibilityId("BasicRadioButton"); + Assert.False(button.GetIsChecked()); + button.Click(); + Assert.True(button.GetIsChecked()); + } + + [Fact] + public void ThreeState_RadioButton_IsChecked_False_When_Other_ThreeState_RadioButton_Checked() + { + var button1 = _session.FindElementByAccessibilityId("ThreeStatesRadioButton1"); + var button2 = _session.FindElementByAccessibilityId("ThreeStatesRadioButton2"); + Assert.True(button1.GetIsChecked()); + Assert.False(button2.GetIsChecked()); + button2.Click(); + Assert.False(button1.GetIsChecked()); + Assert.True(button2.GetIsChecked()); + } + + } +}