Browse Source

Expose Calendar day button selection state to automation (#21540)

* Add CalendarDayButtonAutomationPeer with SelectionItem pattern

* Guard day button selection against None mode and blackout days, add peer tests

* Use NotNullWhen instead of null-forgiving out parameter
pull/21551/head
Jumar Macato 4 months ago
committed by GitHub
parent
commit
269d0f2b8e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 78
      src/Avalonia.Controls/Automation/Peers/CalendarDayButtonAutomationPeer.cs
  2. 3
      src/Avalonia.Controls/Calendar/CalendarDayButton.cs
  3. 166
      tests/Avalonia.Controls.UnitTests/Automation/CalendarDayButtonAutomationPeerTests.cs

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);
}
}

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);
}
}
Loading…
Cancel
Save