Browse Source

Merge branch 'master' into fix/macos-cgs-wedge-render-timer

pull/21453/head
Brian Pham 3 months ago
committed by GitHub
parent
commit
0e56e52ce3
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 3
      native/Avalonia.Native/src/OSX/automation.mm
  2. 78
      src/Avalonia.Controls/Automation/Peers/CalendarDayButtonAutomationPeer.cs
  3. 55
      src/Avalonia.Controls/Automation/Peers/MenuItemAutomationPeer.cs
  4. 3
      src/Avalonia.Controls/Calendar/CalendarDayButton.cs
  5. 166
      tests/Avalonia.Controls.UnitTests/Automation/CalendarDayButtonAutomationPeerTests.cs
  6. 95
      tests/Avalonia.Controls.UnitTests/Automation/MenuItemAutomationPeerTests.cs

3
native/Avalonia.Native/src/OSX/automation.mm

@ -96,7 +96,7 @@
case AutomationEdit: return NSAccessibilityTextFieldRole;
case AutomationHyperlink: return NSAccessibilityLinkRole;
case AutomationImage: return NSAccessibilityImageRole;
case AutomationListItem: return NSAccessibilityGroupRole;
case AutomationListItem: return NSAccessibilityRowRole;
case AutomationList: return NSAccessibilityListRole;
case AutomationMenu: return NSAccessibilityMenuBarRole;
case AutomationMenuBar: return NSAccessibilityMenuBarRole;
@ -141,6 +141,7 @@
auto controlType = _peer->GetAutomationControlType();
switch (controlType) {
case AutomationList: return @"AXContentList";
case AutomationListItem: return NSAccessibilityTableRowSubrole;
}
auto landmarkType = _peer->GetLandmarkType();

78
src/Avalonia.Controls/Automation/Peers/CalendarDayButtonAutomationPeer.cs

@ -0,0 +1,78 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Automation.Provider;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
namespace Avalonia.Automation.Peers
{
public class CalendarDayButtonAutomationPeer : ButtonAutomationPeer, ISelectionItemProvider
{
public CalendarDayButtonAutomationPeer(CalendarDayButton owner)
: base(owner)
{
}
public new CalendarDayButton Owner => (CalendarDayButton)base.Owner;
public bool IsSelected => Owner.IsSelected;
public ISelectionProvider? SelectionContainer
=> Owner.Owner is { } calendar
? GetOrCreate(calendar).GetProvider<ISelectionProvider>()
: null;
public void Select()
{
EnsureEnabled();
if (TryGetSelectableDate(out var calendar, out var date))
calendar.SelectedDate = date;
}
void ISelectionItemProvider.AddToSelection()
{
EnsureEnabled();
if (!TryGetSelectableDate(out var calendar, out var date) ||
calendar.SelectedDates.Contains(date))
{
return;
}
if (calendar.SelectionMode == CalendarSelectionMode.SingleDate)
calendar.SelectedDate = date;
else
calendar.SelectedDates.Add(date);
}
void ISelectionItemProvider.RemoveFromSelection()
{
EnsureEnabled();
if (Owner.Owner is { SelectionMode: not CalendarSelectionMode.None } calendar &&
Owner.DataContext is DateTime date)
{
calendar.SelectedDates.Remove(date);
}
}
private bool TryGetSelectableDate([NotNullWhen(true)] out Calendar? calendar, out DateTime date)
{
if (Owner.Owner is { SelectionMode: not CalendarSelectionMode.None } owner &&
Owner.DataContext is DateTime value &&
!Owner.IsBlackout)
{
calendar = owner;
date = value;
return true;
}
calendar = null;
date = default;
return false;
}
protected override string GetClassNameCore() => nameof(CalendarDayButton);
}
}

55
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<bool>()),
ToState(e.GetNewValue<bool>()));
}
}
private static ToggleState ToState(bool value)
=> value ? ToggleState.On : ToggleState.Off;
}
}

3
src/Avalonia.Controls/Calendar/CalendarDayButton.cs

@ -5,6 +5,7 @@
using System;
using System.Globalization;
using Avalonia.Automation.Peers;
using Avalonia.Controls.Metadata;
using Avalonia.Input;
@ -157,6 +158,8 @@ namespace Avalonia.Controls.Primitives
SetPseudoClasses();
}
protected override AutomationPeer OnCreateAutomationPeer() => new CalendarDayButtonAutomationPeer(this);
private void SetPseudoClasses()
{
if (_ignoringMouseOverState)

166
tests/Avalonia.Controls.UnitTests/Automation/CalendarDayButtonAutomationPeerTests.cs

@ -0,0 +1,166 @@
using System;
using Avalonia.Automation.Peers;
using Avalonia.Automation.Provider;
using Avalonia.Controls.Primitives;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests.Automation;
public class CalendarDayButtonAutomationPeerTests : ScopedTestBase
{
private static readonly DateTime Date1 = new(2026, 6, 5);
private static readonly DateTime Date2 = new(2026, 6, 10);
private static (Calendar, CalendarDayButton, ISelectionItemProvider) CreateTarget(
CalendarSelectionMode mode, DateTime? date = null)
{
var calendar = new Calendar { SelectionMode = mode };
var dayButton = new CalendarDayButton { Owner = calendar, DataContext = date ?? Date1 };
var peer = ControlAutomationPeer.CreatePeerForElement(dayButton);
return (calendar, dayButton, Assert.IsAssignableFrom<ISelectionItemProvider>(peer));
}
[Fact]
public void Creates_CalendarDayButtonAutomationPeer()
{
var peer = ControlAutomationPeer.CreatePeerForElement(new CalendarDayButton());
Assert.IsType<CalendarDayButtonAutomationPeer>(peer);
Assert.IsAssignableFrom<ISelectionItemProvider>(peer);
Assert.IsAssignableFrom<IInvokeProvider>(peer);
}
[Fact]
public void IsSelected_Reflects_Owner_State()
{
var (_, dayButton, provider) = CreateTarget(CalendarSelectionMode.SingleDate);
Assert.False(provider.IsSelected);
dayButton.IsSelected = true;
Assert.True(provider.IsSelected);
}
[Fact]
public void SelectionContainer_Is_Calendar_Peer()
{
var (calendar, _, provider) = CreateTarget(CalendarSelectionMode.SingleDate);
var container = provider.SelectionContainer;
Assert.NotNull(container);
Assert.Same(ControlAutomationPeer.CreatePeerForElement(calendar), container);
}
[Fact]
public void Select_Sets_SelectedDate()
{
var (calendar, _, provider) = CreateTarget(CalendarSelectionMode.SingleDate);
provider.Select();
Assert.Equal(Date1, calendar.SelectedDate);
}
[Fact]
public void Select_Replaces_Existing_Selection_In_SingleDate_Mode()
{
var (calendar, _, provider) = CreateTarget(CalendarSelectionMode.SingleDate);
calendar.SelectedDate = Date2;
provider.Select();
Assert.Equal(Date1, calendar.SelectedDate);
Assert.Equal(1, calendar.SelectedDates.Count);
}
[Fact]
public void Select_Is_NoOp_When_SelectionMode_None()
{
var (calendar, _, provider) = CreateTarget(CalendarSelectionMode.None);
provider.Select();
Assert.Null(calendar.SelectedDate);
Assert.Empty(calendar.SelectedDates);
}
[Fact]
public void Select_Is_NoOp_For_Blackout_Day()
{
var (calendar, dayButton, provider) = CreateTarget(CalendarSelectionMode.SingleDate);
dayButton.IsBlackout = true;
provider.Select();
Assert.Null(calendar.SelectedDate);
}
[Fact]
public void AddToSelection_Replaces_Selection_In_SingleDate_Mode()
{
var (calendar, _, provider) = CreateTarget(CalendarSelectionMode.SingleDate);
calendar.SelectedDate = Date2;
provider.AddToSelection();
Assert.Equal(Date1, calendar.SelectedDate);
Assert.Equal(1, calendar.SelectedDates.Count);
}
[Fact]
public void AddToSelection_Appends_In_MultipleRange_Mode()
{
var (calendar, _, provider) = CreateTarget(CalendarSelectionMode.MultipleRange);
calendar.SelectedDates.Add(Date2);
provider.AddToSelection();
Assert.Equal(2, calendar.SelectedDates.Count);
Assert.Contains(Date1, calendar.SelectedDates);
Assert.Contains(Date2, calendar.SelectedDates);
}
[Fact]
public void AddToSelection_Is_NoOp_When_SelectionMode_None()
{
var (calendar, _, provider) = CreateTarget(CalendarSelectionMode.None);
provider.AddToSelection();
Assert.Empty(calendar.SelectedDates);
}
[Fact]
public void AddToSelection_Is_NoOp_For_Blackout_Day()
{
var (calendar, dayButton, provider) = CreateTarget(CalendarSelectionMode.MultipleRange);
dayButton.IsBlackout = true;
provider.AddToSelection();
Assert.Empty(calendar.SelectedDates);
}
[Fact]
public void RemoveFromSelection_Removes_Date()
{
var (calendar, _, provider) = CreateTarget(CalendarSelectionMode.MultipleRange);
calendar.SelectedDates.Add(Date1);
calendar.SelectedDates.Add(Date2);
provider.RemoveFromSelection();
Assert.Equal(Date2, Assert.Single(calendar.SelectedDates));
}
[Fact]
public void RemoveFromSelection_Is_NoOp_When_SelectionMode_None()
{
var (calendar, _, provider) = CreateTarget(CalendarSelectionMode.None);
provider.RemoveFromSelection();
Assert.Empty(calendar.SelectedDates);
}
}

95
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<IToggleProvider>();
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<IToggleProvider>());
}
[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<IToggleProvider>());
}
[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);
}
}
Loading…
Cancel
Save