From dce799ba10c5fdb60d053927821e19e013146fe6 Mon Sep 17 00:00:00 2001 From: Daniil Pavliuchyk Date: Tue, 21 Feb 2023 17:10:50 +0200 Subject: [PATCH 1/4] RadioButtonAutomationPeer WIP --- .../Peers/RadioButtonAutomationPeer.cs | 70 +++++++++++++++++++ .../SelectionItemPatternIdentifiers.cs | 16 +++++ src/Avalonia.Controls/RadioButton.cs | 7 ++ .../Automation/AutomationNode.cs | 2 + 4 files changed, 95 insertions(+) create mode 100644 src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs create mode 100644 src/Avalonia.Controls/Automation/SelectionItemPatternIdentifiers.cs diff --git a/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs new file mode 100644 index 00000000000..ea4e37adba9 --- /dev/null +++ b/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs @@ -0,0 +1,70 @@ +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 + { + get + { + return 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(); From 5e13280b42ebeb41c64fdd38610c088203c0bb2c Mon Sep 17 00:00:00 2001 From: Daniil Pavliuchyk Date: Wed, 22 Feb 2023 13:41:19 +0200 Subject: [PATCH 2/4] Fix warning --- .../Automation/Peers/RadioButtonAutomationPeer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs index ea4e37adba9..b7b7adfd523 100644 --- a/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs @@ -13,7 +13,7 @@ public RadioButtonAutomationPeer(RadioButton owner) : base(owner) { if (e.Property == RadioButton.IsCheckedProperty) { - RaiseToggleStatePropertyChangedEvent((bool)e.OldValue, (bool)e.NewValue); + RaiseToggleStatePropertyChangedEvent((bool?)e.OldValue, (bool?)e.NewValue); } }; From 8a771855db300bd73902d58d97c5822da517dc29 Mon Sep 17 00:00:00 2001 From: Daniil Pavliuchyk Date: Wed, 22 Feb 2023 15:41:30 +0200 Subject: [PATCH 3/4] Add tests --- samples/IntegrationTestApp/MainWindow.axaml | 10 ++++ .../RadioButtonTests.cs | 48 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 tests/Avalonia.IntegrationTests.Appium/RadioButtonTests.cs 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/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()); + } + + } +} From 05dc0a3559b6a78fcc6010df1ebfb59a4baa0756 Mon Sep 17 00:00:00 2001 From: Daniil Pavliuchyk Date: Thu, 23 Feb 2023 14:36:55 +0200 Subject: [PATCH 4/4] Fix formatting --- .../Automation/Peers/RadioButtonAutomationPeer.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs index b7b7adfd523..b0f83c1f2ad 100644 --- a/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/RadioButtonAutomationPeer.cs @@ -15,7 +15,6 @@ public RadioButtonAutomationPeer(RadioButton owner) : base(owner) { RaiseToggleStatePropertyChangedEvent((bool?)e.OldValue, (bool?)e.NewValue); } - }; } @@ -31,13 +30,7 @@ override protected AutomationControlType GetAutomationControlTypeCore() public bool IsSelected => ((RadioButton)Owner).IsChecked == true; - public ISelectionProvider? SelectionContainer - { - get - { - return null; - } - } + public ISelectionProvider? SelectionContainer => null; public void AddToSelection() {