Files

2160 lines
82 KiB
C#

using System.Xml.Linq;
using AtomUI.Desktop.Controls.CalendarView;
using AtomUI.Desktop.Controls.CalendarView.Infrastructure;
using AtomUI.Desktop.Controls.CalendarView.State;
using AtomUI.Desktop.Controls.Primitives;
using AtomUI.Localization;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Threading;
using Avalonia.VisualTree;
using Shouldly;
using Xunit;
using AvaloniaGrid = Avalonia.Controls.Grid;
using AvaloniaWindow = Avalonia.Controls.Window;
using PickerCalendar = AtomUI.Desktop.Controls.CalendarView.Calendar;
using PickerCalendarMode = AtomUI.Desktop.Controls.CalendarView.CalendarMode;
using PickerCalendarButton = AtomUI.Desktop.Controls.CalendarView.CalendarButton;
using PickerCalendarItem = AtomUI.Desktop.Controls.CalendarView.CalendarItem;
using PickerCalendarDayButton = AtomUI.Desktop.Controls.CalendarView.CalendarDayButton;
using PickerDualMonthCalendarItem = AtomUI.Desktop.Controls.CalendarView.DualMonthCalendarItem;
namespace AtomUI.Desktop.Controls.Tests.DatePickers;
public class CalendarViewStateTests
{
static CalendarViewStateTests()
{
AvaloniaTestApp.EnsureInitialized();
}
[Fact]
public void CalendarItem_Declares_TemplateParts_Used_By_Default_Themes()
{
RunOnUIThread(() =>
{
var expectedParts = new Dictionary<string, Type>
{
["PART_HeaderButton"] = typeof(HeadTextButton),
["PART_HeaderLayout"] = typeof(Panel),
["PART_MonthViewLayout"] = typeof(UniformGrid),
["PART_MonthView"] = typeof(AvaloniaGrid),
["PART_PreviousButton"] = typeof(IconButton),
["PART_PreviousMonthButton"] = typeof(IconButton),
["PART_NextButton"] = typeof(IconButton),
["PART_NextMonthButton"] = typeof(IconButton),
["PART_YearView"] = typeof(AvaloniaGrid)
};
AssertTemplateParts(typeof(PickerCalendarItem), expectedParts);
});
}
[Fact]
public void DualMonthCalendarItem_Declares_Secondary_TemplateParts()
{
RunOnUIThread(() =>
{
var expectedParts = new Dictionary<string, Type>
{
["PART_SecondaryMonthView"] = typeof(AvaloniaGrid),
["PART_SecondaryHeaderButton"] = typeof(HeadTextButton),
["PART_SecondaryPreviousButton"] = typeof(IconButton),
["PART_SecondaryPreviousMonthButton"] = typeof(IconButton),
["PART_SecondaryNextButton"] = typeof(IconButton),
["PART_SecondaryNextMonthButton"] = typeof(IconButton),
["PART_YearViewLayout"] = typeof(UniformGrid),
["PART_SecondaryYearView"] = typeof(AvaloniaGrid)
};
AssertTemplateParts(typeof(PickerDualMonthCalendarItem), expectedParts);
});
}
[Fact]
public void DualMonthRangeCalendar_Theme_BasedOn_CalendarTheme()
{
var source = ReadRepoFile("src/AtomUI.Desktop.Controls/DatePicker/Themes/CalendarView/DualMonthRangeCalendarTheme.axaml");
source.ShouldContain("<atom:CalendarTheme TargetType=\"calendarView:DualMonthRangeCalendar\" />");
source.ShouldNotContain("<atom:CalendarItemTheme TargetType=\"calendarView:DualMonthRangeCalendar\" />");
}
[Theory]
[InlineData("src/AtomUI.Desktop.Controls/DatePicker/Themes/CalendarView/CalendarDayButtonTheme.axaml")]
[InlineData("src/AtomUI.Desktop.Controls/DatePicker/Themes/CalendarView/CalendarButtonTheme.axaml")]
public void Calendar_Cell_Themes_Define_Explicit_Disabled_Visuals(string relativePath)
{
var source = ReadRepoFile(relativePath);
var document = XDocument.Parse(source);
var avaloniaNamespace = XNamespace.Get("https://github.com/avaloniaui");
var disabledStyle = document.Descendants(avaloniaNamespace + "Style")
.Single(style => (string?)style.Attribute("Selector") == "^:disabled");
var disabledBackgroundStyle = disabledStyle.Elements(avaloniaNamespace + "Style")
.Single(style => (string?)style.Attribute("Selector") ==
"^ /template/ Border#DisabledBackground");
source.ShouldContain("<Style Selector=\"^:disabled\">");
source.ShouldContain("<Border Name=\"DisabledBackground\"");
source.ShouldContain("CellBgDisabled");
source.ShouldContain("ColorTextDisabled");
var disabledBackground = document.Descendants(avaloniaNamespace + "Border")
.Single(border => (string?)border.Attribute("Name") == "DisabledBackground");
disabledBackground.Attribute("CornerRadius").ShouldBeNull();
disabledBackground.Attribute("Margin").ShouldBeNull();
disabledBackground.Attribute("Grid.ColumnSpan")?.Value.ShouldBe("2");
disabledStyle.Elements(avaloniaNamespace + "Setter")
.Any(setter => (string?)setter.Attribute("Property") == "Background" &&
((string?)setter.Attribute("Value"))?.Contains("CellBgDisabled", StringComparison.Ordinal) == true)
.ShouldBeFalse();
disabledBackgroundStyle.Elements(avaloniaNamespace + "Setter")
.Any(setter => (string?)setter.Attribute("Property") == "Background" &&
((string?)setter.Attribute("Value"))?.Contains("CellBgDisabled", StringComparison.Ordinal) == true)
.ShouldBeTrue();
}
[Theory]
[InlineData("src/AtomUI.Desktop.Controls/DatePicker/Themes/DatePickerPresenterTheme.axaml")]
[InlineData("src/AtomUI.Desktop.Controls/DatePicker/Themes/DualMonthRangeDatePickerPresenterTheme.axaml")]
[InlineData("src/AtomUI.Desktop.Controls/DatePicker/Themes/TimedRangeDatePickerPresenterTheme.axaml")]
public void DatePicker_Presenter_Themes_Do_Not_Bind_Raw_Selected_Values_To_Calendar(string relativePath)
{
var source = ReadRepoFile(relativePath);
source.ShouldNotContain("SelectedDate=\"{TemplateBinding SelectedDateTime}\"");
source.ShouldNotContain("SecondarySelectedDate=\"{TemplateBinding SecondarySelectedDateTime}\"");
}
[Fact]
public void RangeDatePickerPresenter_Now_With_Confirm_Confirms_Active_Range_Part()
{
var presenter = new TestRangeDatePickerPresenter
{
IsNeedConfirm = true,
IsRangeStartActive = true
};
var rangePartConfirmedCount = 0;
presenter.RangePartConfirmed += (_, _) => rangePartConfirmedCount++;
presenter.NotifyNowButtonClickedForTest();
presenter.SelectedDateTime.ShouldNotBeNull();
rangePartConfirmedCount.ShouldBe(1);
}
[Fact]
public void RangeDatePickerPresenter_Today_And_Now_Do_Not_Select_Out_Of_Range_Values()
{
var presenter = new TestRangeDatePickerPresenter
{
MinDate = DateTime.Today.AddDays(1),
MaxDate = DateTime.Today.AddDays(10)
};
var rangePartConfirmedCount = 0;
presenter.RangePartConfirmed += (_, _) => rangePartConfirmedCount++;
presenter.NotifyTodayButtonClickedForTest();
presenter.NotifyNowButtonClickedForTest();
presenter.SelectedDateTime.ShouldBeNull();
presenter.SecondarySelectedDateTime.ShouldBeNull();
rangePartConfirmedCount.ShouldBe(0);
}
[Fact]
public void TimedRangeDatePickerPresenter_Now_With_Confirm_Only_Selects_Active_Date()
{
var presenter = new TestTimedRangeDatePickerPresenter
{
IsNeedConfirm = true,
IsRangeStartActive = true
};
var rangePartConfirmedCount = 0;
presenter.RangePartConfirmed += (_, _) => rangePartConfirmedCount++;
presenter.NotifyNowButtonClickedForTest();
presenter.SelectedDateTime.ShouldNotBeNull();
rangePartConfirmedCount.ShouldBe(0);
}
[Fact]
public void CalendarItem_ClearGeneratedMonthView_Releases_Owner_FocusButton()
{
RunOnUIThread(() =>
{
var calendar = new PickerCalendar();
var item = new TestCalendarItem();
var monthView = new AvaloniaGrid();
var dayButton = new PickerCalendarDayButton
{
Owner = calendar,
IsCurrent = true
};
calendar.FocusButton = dayButton;
monthView.Children.Add(dayButton);
item.ClearGeneratedMonthViewForTest(monthView);
calendar.FocusButton.ShouldBeNull();
dayButton.Owner.ShouldBeNull();
dayButton.IsCurrent.ShouldBeFalse();
monthView.Children.Count.ShouldBe(0);
});
}
[Fact]
public void CalendarItem_UpdateYearViewSelection_Tolerates_Missing_Previous_FocusButton()
{
RunOnUIThread(() =>
{
var selectedMonth = new DateTime(2026, 8, 1);
var calendar = new PickerCalendar
{
DisplayMode = PickerCalendarMode.Year
};
var item = new TestCalendarItem
{
Owner = calendar
};
var monthButton = new PickerCalendarButton
{
DataContext = selectedMonth
};
item.UpdateYearViewSelection(monthButton);
calendar.FocusCalendarButton.ShouldBe(monthButton);
calendar.SelectedMonth.ShouldBe(selectedMonth);
});
}
[Theory]
[InlineData(DatePickerMode.Month, PickerCalendarMode.Year, 2026, 5, 1, 2027, 8, 1)]
[InlineData(DatePickerMode.Quarter, PickerCalendarMode.Year, 2026, 4, 1, 2027, 7, 1)]
[InlineData(DatePickerMode.Year, PickerCalendarMode.Decade, 2026, 1, 1, 2032, 1, 1)]
public void RangeCalendarItem_CalendarButton_Hover_Updates_Range_Preview_Date(
DatePickerMode pickerMode,
PickerCalendarMode displayMode,
int startYear,
int startMonth,
int startDay,
int hoverYear,
int hoverMonth,
int hoverDay)
{
RunOnUIThread(() =>
{
var startDate = new DateTime(startYear, startMonth, startDay);
var hoverDate = new DateTime(hoverYear, hoverMonth, hoverDay);
var calendar = new RangeCalendar
{
PickerMode = pickerMode,
DisplayMode = displayMode,
SelectedDate = startDate,
IsSelectRangeStart = false
};
var item = new TestRangeCalendarItem
{
Owner = calendar
};
var hoverButton = new PickerCalendarButton
{
DataContext = hoverDate,
IsEnabled = true
};
item.HandleMonthMouseEnteredForTest(hoverButton);
calendar.HoverDateTime.ShouldBe(hoverDate);
var state = calendar.SyncAndGetCurrentViewState();
state.RangeSelection.TryGetPreviewRange(out var previewStart, out var previewEnd).ShouldBeTrue();
previewStart.ShouldBe(startDate);
previewEnd.ShouldBe(hoverDate);
});
}
[Theory]
[InlineData(DatePickerMode.Date, PickerCalendarMode.Month)]
[InlineData(DatePickerMode.Week, PickerCalendarMode.Month)]
[InlineData(DatePickerMode.Month, PickerCalendarMode.Year)]
[InlineData(DatePickerMode.Quarter, PickerCalendarMode.Year)]
[InlineData(DatePickerMode.Year, PickerCalendarMode.Decade)]
public void Calendar_PickerMode_Change_Uses_Target_DisplayMode(DatePickerMode pickerMode, PickerCalendarMode expectedDisplayMode)
{
RunOnUIThread(() =>
{
var calendar = new PickerCalendar
{
PickerMode = pickerMode
};
calendar.DisplayMode.ShouldBe(expectedDisplayMode);
});
}
[Theory]
[InlineData(DatePickerMode.Month, 7, 2026, 7, 1)]
[InlineData(DatePickerMode.Quarter, 8, 2026, 7, 1)]
public void CalendarItem_Year_Mode_Target_Picker_Selects_Unit_Without_Drilling_Down(
DatePickerMode pickerMode,
int sourceMonth,
int expectedYear,
int expectedMonth,
int expectedDay)
{
RunOnUIThread(() =>
{
var calendar = new PickerCalendar
{
PickerMode = pickerMode,
DisplayMode = PickerCalendarMode.Year
};
var item = new TestCalendarItem
{
Owner = calendar
};
var button = new PickerCalendarButton
{
DataContext = new DateTime(2026, sourceMonth, 1)
};
DateTime? emittedDate = null;
calendar.DateSelected += (_, args) => emittedDate = args.Date;
item.HandleMonthCalendarButtonMouseUpForTest(button);
var expectedDate = new DateTime(expectedYear, expectedMonth, expectedDay);
calendar.SelectedDate.ShouldBe(expectedDate);
emittedDate.ShouldBe(expectedDate);
calendar.DisplayMode.ShouldBe(PickerCalendarMode.Year);
});
}
[Fact]
public void CalendarItem_Year_Picker_Selects_Year_From_Decade_Mode()
{
RunOnUIThread(() =>
{
var calendar = new PickerCalendar
{
PickerMode = DatePickerMode.Year,
DisplayMode = PickerCalendarMode.Decade
};
var item = new TestCalendarItem
{
Owner = calendar
};
var button = new PickerCalendarButton
{
DataContext = new DateTime(2028, 1, 1)
};
DateTime? emittedDate = null;
calendar.DateSelected += (_, args) => emittedDate = args.Date;
item.HandleMonthCalendarButtonMouseUpForTest(button);
var expectedDate = new DateTime(2028, 1, 1);
calendar.SelectedDate.ShouldBe(expectedDate);
emittedDate.ShouldBe(expectedDate);
calendar.DisplayMode.ShouldBe(PickerCalendarMode.Decade);
});
}
[Theory]
[InlineData(DatePickerMode.Month, PickerCalendarMode.Year, 2026, 8, 1, 2026, 8, 1)]
[InlineData(DatePickerMode.Quarter, PickerCalendarMode.Year, 2026, 8, 1, 2026, 7, 1)]
[InlineData(DatePickerMode.Year, PickerCalendarMode.Decade, 2028, 1, 1, 2028, 1, 1)]
public void Calendar_Enter_Key_Selects_Target_Picker_Unit_Without_Drilling_Down(
DatePickerMode pickerMode,
PickerCalendarMode displayMode,
int sourceYear,
int sourceMonth,
int sourceDay,
int expectedYear,
int expectedMonth,
int expectedDay)
{
RunOnUIThread(() =>
{
var sourceDate = new DateTime(sourceYear, sourceMonth, sourceDay);
var calendar = new PickerCalendar
{
PickerMode = pickerMode,
DisplayMode = displayMode
};
if (displayMode == PickerCalendarMode.Year)
{
calendar.SelectedMonth = sourceDate;
}
else
{
calendar.SelectedYear = sourceDate;
}
DateTime? emittedDate = null;
calendar.DateSelected += (_, args) => emittedDate = args.Date;
var handled = calendar.ProcessCalendarKey(new KeyEventArgs
{
Key = Key.Enter,
PhysicalKey = PhysicalKey.Enter,
KeyModifiers = KeyModifiers.None
});
var expectedDate = new DateTime(expectedYear, expectedMonth, expectedDay);
handled.ShouldBeTrue();
calendar.SelectedDate.ShouldBe(expectedDate);
emittedDate.ShouldBe(expectedDate);
calendar.DisplayMode.ShouldBe(displayMode);
});
}
[Theory]
[InlineData(Key.Down, 2, 5)]
[InlineData(Key.Up, 5, 2)]
public void Calendar_Year_Mode_Up_Down_Keys_Follow_Month_Selection_Panel_Columns(
Key key,
int sourceMonth,
int expectedMonth)
{
RunOnUIThread(() =>
{
var calendar = new PickerCalendar
{
PickerMode = DatePickerMode.Month,
DisplayMode = PickerCalendarMode.Year
};
calendar.SelectedMonth = new DateTime(2026, sourceMonth, 1);
var handled = calendar.ProcessCalendarKey(new KeyEventArgs
{
Key = key,
PhysicalKey = key == Key.Down ? PhysicalKey.ArrowDown : PhysicalKey.ArrowUp,
KeyModifiers = KeyModifiers.None
});
handled.ShouldBeTrue();
calendar.SelectedMonth.ShouldBe(new DateTime(2026, expectedMonth, 1));
});
}
[Fact]
public void Calendar_SelectedDate_Change_Refreshes_Selected_Day_Button()
{
RunOnUIThread(() =>
{
var calendar = new PickerCalendar
{
DisplayDate = new DateTime(2026, 6, 1),
SelectedDate = new DateTime(2026, 6, 10)
};
calendar.SelectedDate.ShouldBe(new DateTime(2026, 6, 10));
ShowInWindow(calendar, () =>
{
var firstButton = FindDayButton(calendar, new DateTime(2026, 6, 10));
var state = calendar.SyncAndGetCurrentViewState();
IsSelected(firstButton).ShouldBeTrue(
$"SelectedDate={calendar.SelectedDate:yyyy-MM-dd}; StateSelected={state.SelectedDate:yyyy-MM-dd}; " +
$"StatePickerMode={state.PickerMode}; ButtonIsSelected={firstButton.IsSelected}");
calendar.SelectedDate = new DateTime(2026, 6, 12);
Dispatcher.UIThread.RunJobs();
IsSelected(FindDayButton(calendar, new DateTime(2026, 6, 10))).ShouldBeFalse();
IsSelected(FindDayButton(calendar, new DateTime(2026, 6, 12))).ShouldBeTrue();
});
});
}
[Fact]
public void RangeCalendar_SecondarySelectedDate_Change_Refreshes_Range_Highlight()
{
RunOnUIThread(() =>
{
var calendar = new RangeCalendar
{
DisplayDate = new DateTime(2026, 6, 1),
SelectedDate = new DateTime(2026, 6, 10),
SecondarySelectedDate = new DateTime(2026, 6, 15)
};
ShowInWindow(calendar, () =>
{
IsRangeEnd(FindDayButton(calendar, new DateTime(2026, 6, 15))).ShouldBeTrue();
calendar.SecondarySelectedDate = new DateTime(2026, 6, 18);
Dispatcher.UIThread.RunJobs();
IsSelected(FindDayButton(calendar, new DateTime(2026, 6, 15))).ShouldBeFalse();
IsRangeEnd(FindDayButton(calendar, new DateTime(2026, 6, 15))).ShouldBeFalse();
IsRangeMiddle(FindDayButton(calendar, new DateTime(2026, 6, 15))).ShouldBeTrue();
IsSelected(FindDayButton(calendar, new DateTime(2026, 6, 18))).ShouldBeTrue();
IsRangeEnd(FindDayButton(calendar, new DateTime(2026, 6, 18))).ShouldBeTrue();
});
});
}
[Fact]
public void RangeCalendar_HoverDate_Does_Not_Apply_Committed_Range_End_To_Hover_Button()
{
RunOnUIThread(() =>
{
var calendar = new RangeCalendar
{
DisplayDate = new DateTime(2026, 7, 1),
SelectedDate = new DateTime(2026, 7, 15),
HoverDateTime = new DateTime(2026, 7, 16),
IsSelectRangeStart = false
};
ShowInWindow(calendar, () =>
{
var hoverButton = FindDayButton(calendar, new DateTime(2026, 7, 16));
IsSelected(hoverButton).ShouldBeFalse();
IsRangeEnd(hoverButton).ShouldBeFalse();
IsRangePreviewEnd(hoverButton).ShouldBeTrue();
});
});
}
[Fact]
public void RangeCalendar_Active_Start_HoverDate_Does_Not_Apply_Committed_Range_Start_To_Hover_Button()
{
RunOnUIThread(() =>
{
var calendar = new RangeCalendar
{
DisplayDate = new DateTime(2026, 7, 1),
SecondarySelectedDate = new DateTime(2026, 7, 16),
HoverDateTime = new DateTime(2026, 7, 8),
IsSelectRangeStart = true
};
ShowInWindow(calendar, () =>
{
var hoverButton = FindDayButton(calendar, new DateTime(2026, 7, 8));
var endButton = FindDayButton(calendar, new DateTime(2026, 7, 16));
IsSelected(hoverButton).ShouldBeFalse();
IsRangeStart(hoverButton).ShouldBeFalse();
IsRangePreviewStart(hoverButton).ShouldBeTrue();
IsSelected(endButton).ShouldBeFalse();
IsRangeEnd(endButton).ShouldBeFalse();
IsRangePreviewEnd(endButton).ShouldBeTrue();
});
});
}
[Fact]
public void Calendar_DisplayDateStart_After_DisplayDateEnd_Clamps_End_To_Start()
{
RunOnUIThread(() =>
{
var calendar = new PickerCalendar
{
DisplayDate = new DateTime(2026, 6, 1),
DisplayDateEnd = new DateTime(2026, 6, 10),
DisplayDateStart = new DateTime(2026, 6, 20)
};
calendar.DisplayDateEnd.ShouldBe(new DateTime(2026, 6, 20));
});
}
[Fact]
public void Calendar_SelectedDate_Before_DisplayDateStart_Throws_And_Does_Not_Expand_Range()
{
RunOnUIThread(() =>
{
var calendar = new PickerCalendar
{
DisplayDateStart = new DateTime(2026, 6, 10),
DisplayDateEnd = new DateTime(2026, 6, 20)
};
Should.Throw<ArgumentOutOfRangeException>(() =>
{
calendar.SelectedDate = new DateTime(2026, 6, 5);
});
calendar.DisplayDateStart.ShouldBe(new DateTime(2026, 6, 10));
calendar.DisplayDateEnd.ShouldBe(new DateTime(2026, 6, 20));
calendar.SelectedDate.ShouldBeNull();
});
}
[Fact]
public void Calendar_Type_Initialization_Does_Not_Read_ThemeManager_Static_State()
{
var property = PickerCalendar.FirstDayOfWeekProperty;
property.Name.ShouldBe(nameof(PickerCalendar.FirstDayOfWeek));
}
[Fact]
public void CalendarButton_Default_Content_Is_Culture_Free()
{
RunOnUIThread(() =>
{
var button = new PickerCalendarButton();
button.Content.ShouldBe(string.Empty);
});
}
[Fact]
public void Calendar_CurrentViewState_Tracks_Normalized_Display_Range()
{
RunOnUIThread(() =>
{
var calendar = new PickerCalendar
{
DisplayDate = new DateTime(2026, 6, 1),
DisplayDateEnd = new DateTime(2026, 6, 10),
DisplayDateStart = new DateTime(2026, 6, 20)
};
var state = calendar.SyncAndGetCurrentViewState();
state.DisplayDateStart.ShouldBe(new DateTime(2026, 6, 20));
state.DisplayDateEnd.ShouldBe(new DateTime(2026, 6, 20));
state.DisplayDate.ShouldBe(new DateTime(2026, 6, 1));
});
}
[Fact]
public void Calendar_CurrentViewState_Tracks_Week_Start_And_CrossMonth_SelectedDate()
{
RunOnUIThread(() =>
{
var calendar = new PickerCalendar
{
DisplayDate = new DateTime(2026, 6, 1),
FirstDayOfWeek = DayOfWeek.Monday
};
calendar.SyncAndGetCurrentViewState().FirstDayOfWeek.ShouldBe(DayOfWeek.Monday);
calendar.FirstDayOfWeek = DayOfWeek.Sunday;
calendar.SelectedDate = new DateTime(2026, 8, 12);
var state = calendar.SyncAndGetCurrentViewState();
state.FirstDayOfWeek.ShouldBe(DayOfWeek.Sunday);
state.SelectedDate.ShouldBe(new DateTime(2026, 8, 12));
state.DisplayDate.ShouldBe(new DateTime(2026, 8, 1));
});
}
[Fact]
public void RangeCalendar_CurrentViewState_Tracks_Range_Selection()
{
RunOnUIThread(() =>
{
var calendar = new RangeCalendar
{
DisplayDate = new DateTime(2026, 6, 1),
SelectedDate = new DateTime(2026, 6, 10),
SecondarySelectedDate = new DateTime(2026, 6, 15),
HoverDateTime = new DateTime(2026, 6, 18),
IsSelectRangeStart = false
};
var state = calendar.SyncAndGetCurrentViewState();
state.SelectedDate.ShouldBe(new DateTime(2026, 6, 10));
state.SecondarySelectedDate.ShouldBe(new DateTime(2026, 6, 15));
state.RangeSelection.HoverDate.ShouldBe(new DateTime(2026, 6, 18));
state.RangeSelection.ActivePart.ShouldBe(CalendarRangeActivePart.End);
});
}
[Fact]
public void RangeCalendar_End_Selection_Does_Not_Let_Start_Date_Rewind_DisplayDate()
{
RunOnUIThread(() =>
{
var calendar = new RangeCalendar
{
DisplayDate = new DateTime(2026, 8, 1),
IsSelectRangeStart = false
};
calendar.SelectedDate = new DateTime(2026, 7, 12);
calendar.DisplayDate.ShouldBe(new DateTime(2026, 8, 1));
calendar.SyncAndGetCurrentViewState().DisplayDate.ShouldBe(new DateTime(2026, 8, 1));
});
}
[Fact]
public void RangeCalendar_TemplateApply_Does_Not_Let_Start_Date_Rewind_DisplayDate_When_End_Is_Active()
{
RunOnUIThread(() =>
{
var calendar = new RangeCalendar
{
SelectedDate = new DateTime(2026, 7, 12),
DisplayDate = new DateTime(2026, 8, 1),
IsSelectRangeStart = false
};
ShowInWindow(calendar, () =>
{
calendar.DisplayDate.ShouldBe(new DateTime(2026, 8, 1));
calendar.SyncAndGetCurrentViewState().DisplayDate.ShouldBe(new DateTime(2026, 8, 1));
});
});
}
[Fact]
public void RangeDatePicker_Open_End_Part_Does_Not_Let_Start_Date_Rewind_Calendar_DisplayDate()
{
RunOnUIThread(() =>
{
var picker = new TestRangeDatePicker
{
RangeStartSelectedDate = new DateTime(2024, 1, 12)
};
picker.RangeActivatedPart = RangeActivatedPart.End;
var presenter = picker.CreatePickerPresenterForTest();
picker.NotifyPickerOpenedForTest();
ShowInWindow(presenter, () =>
{
var calendar = presenter.GetVisualDescendants()
.OfType<DualMonthRangeCalendar>()
.Single();
calendar.IsSelectRangeStart.ShouldBeFalse();
DateTimeHelper.CompareYearMonth(calendar.DisplayDate, picker.RangeStartSelectedDate.Value)
.ShouldNotBe(0);
});
});
}
[Fact]
public void DatePicker_Open_With_PickerDisplayDate_Anchors_Calendar_Without_Selecting_Date()
{
RunOnUIThread(() =>
{
var displayDate = new DateTime(2026, 10, 20);
var picker = new TestDatePicker
{
PickerDisplayDate = displayDate
};
var presenter = picker.CreatePickerPresenterForTest();
picker.NotifyPickerOpenedForTest();
ShowInWindow(presenter, () =>
{
var calendar = presenter.GetVisualDescendants()
.OfType<PickerCalendar>()
.Single();
var expectedSelectedMonth = new DateTime(2026, 10, 1);
calendar.DisplayDate.ShouldBe(displayDate);
calendar.SelectedMonth.ShouldBe(expectedSelectedMonth);
calendar.SelectedYear.ShouldBe(expectedSelectedMonth);
calendar.LastSelectedDate.ShouldBe(displayDate);
calendar.SelectedDate.ShouldBeNull();
presenter.SelectedDateTime.ShouldBeNull();
picker.SelectedDateTime.ShouldBeNull();
});
});
}
[Fact]
public void DatePicker_Open_Uses_SelectedDateTime_Before_PickerDisplayDate()
{
RunOnUIThread(() =>
{
var selectedDate = new DateTime(2026, 2, 11);
var picker = new TestDatePicker
{
PickerDisplayDate = new DateTime(2027, 10, 20),
SelectedDateTime = selectedDate
};
var presenter = picker.CreatePickerPresenterForTest();
picker.NotifyPickerOpenedForTest();
ShowInWindow(presenter, () =>
{
var calendar = presenter.GetVisualDescendants()
.OfType<PickerCalendar>()
.Single();
calendar.DisplayDate.ShouldBe(selectedDate);
calendar.SelectedDate.ShouldBe(selectedDate);
presenter.SelectedDateTime.ShouldBe(selectedDate);
picker.SelectedDateTime.ShouldBe(selectedDate);
});
});
}
[Fact]
public void DatePicker_Open_Normalizes_PickerDisplayDate_By_PickerMode()
{
RunOnUIThread(() =>
{
var picker = new TestDatePicker
{
PickerMode = DatePickerMode.Quarter,
PickerDisplayDate = new DateTime(2026, 11, 20, 8, 30, 0)
};
var presenter = picker.CreatePickerPresenterForTest();
picker.NotifyPickerOpenedForTest();
ShowInWindow(presenter, () =>
{
var calendar = presenter.GetVisualDescendants()
.OfType<PickerCalendar>()
.Single();
var expectedDisplayDate = new DateTime(2026, 10, 1);
calendar.DisplayDate.ShouldBe(expectedDisplayDate);
calendar.SelectedMonth.ShouldBe(expectedDisplayDate);
calendar.SelectedYear.ShouldBe(expectedDisplayDate);
calendar.LastSelectedDate.ShouldBe(expectedDisplayDate);
calendar.SelectedDate.ShouldBeNull();
picker.SelectedDateTime.ShouldBeNull();
});
});
}
[Fact]
public void DatePicker_Date_Bounds_Are_Applied_Before_Valid_Calendar_Selection()
{
RunOnUIThread(() =>
{
var selectedDate = new DateTime(2026, 7, 16, 12, 30, 0);
var picker = new TestDatePicker
{
MinDate = new DateTime(2026, 7, 15, 18, 0, 0),
MaxDate = new DateTime(2026, 7, 20, 6, 0, 0),
SelectedDateTime = selectedDate
};
var presenter = picker.CreatePickerPresenterForTest();
ShowInWindow(presenter, () =>
{
var calendar = presenter.GetVisualDescendants()
.OfType<PickerCalendar>()
.Single();
presenter.MinDate.ShouldBe(picker.MinDate);
presenter.MaxDate.ShouldBe(picker.MaxDate);
calendar.DisplayDateStart.ShouldBe(new DateTime(2026, 7, 15));
calendar.DisplayDateEnd.ShouldBe(new DateTime(2026, 7, 20));
calendar.SelectedDate.ShouldBe(selectedDate.Date);
picker.SelectedDateTime.ShouldBe(selectedDate);
});
});
}
[Fact]
public void DatePicker_Out_Of_Range_Controlled_Value_Remains_External_And_Is_Not_Selected()
{
RunOnUIThread(() =>
{
var selectedDate = new DateTime(2026, 7, 10);
var picker = new TestDatePicker
{
MinDate = new DateTime(2026, 7, 15),
MaxDate = new DateTime(2026, 7, 20),
SelectedDateTime = selectedDate
};
var presenter = picker.CreatePickerPresenterForTest();
ShowInWindow(presenter, () =>
{
var calendar = presenter.GetVisualDescendants()
.OfType<PickerCalendar>()
.Single();
calendar.SelectedDate.ShouldBeNull();
presenter.SelectedDateTime.ShouldBe(selectedDate);
picker.SelectedDateTime.ShouldBe(selectedDate);
presenter.GetVisualDescendants()
.OfType<AtomUI.Desktop.Controls.Button>()
.Single(button => button.Name == "PART_ConfirmButton")
.IsEnabled.ShouldBeFalse();
});
});
}
[Fact]
public void DatePicker_Runtime_Boundary_Change_Clears_Only_Calendar_Selection()
{
RunOnUIThread(() =>
{
var selectedDate = new DateTime(2026, 7, 16);
var picker = new TestDatePicker
{
MinDate = new DateTime(2026, 7, 15),
SelectedDateTime = selectedDate
};
var presenter = picker.CreatePickerPresenterForTest();
ShowInWindow(presenter, () =>
{
var calendar = presenter.GetVisualDescendants()
.OfType<PickerCalendar>()
.Single();
calendar.SelectedDate.ShouldBe(selectedDate);
picker.MinDate = new DateTime(2026, 7, 18);
Dispatcher.UIThread.RunJobs();
calendar.DisplayDateStart.ShouldBe(new DateTime(2026, 7, 18));
calendar.SelectedDate.ShouldBeNull();
presenter.SelectedDateTime.ShouldBe(selectedDate);
picker.SelectedDateTime.ShouldBe(selectedDate);
presenter.GetVisualDescendants()
.OfType<AtomUI.Desktop.Controls.Button>()
.Single(button => button.Name == "PART_ConfirmButton")
.IsEnabled.ShouldBeFalse();
});
});
}
[Fact]
public void DatePicker_Open_Clamps_Display_Anchor_To_Effective_Bounds()
{
RunOnUIThread(() =>
{
var picker = new TestDatePicker
{
PickerMode = DatePickerMode.Month,
PickerDisplayDate = new DateTime(2026, 5, 20),
MinDate = new DateTime(2026, 7, 15),
MaxDate = new DateTime(2026, 10, 31)
};
var presenter = picker.CreatePickerPresenterForTest();
picker.NotifyPickerOpenedForTest();
ShowInWindow(presenter, () =>
{
var calendar = presenter.GetVisualDescendants()
.OfType<PickerCalendar>()
.Single();
calendar.DisplayDate.ShouldBe(new DateTime(2026, 7, 1));
calendar.SelectedDate.ShouldBeNull();
picker.SelectedDateTime.ShouldBeNull();
});
});
}
[Fact]
public void RangeDatePicker_Open_Start_Part_With_PickerDisplayDate_Anchors_Empty_Active_Part()
{
RunOnUIThread(() =>
{
var displayDate = new DateTime(2026, 9, 23);
var picker = new TestRangeDatePicker
{
PickerDisplayDate = displayDate
};
picker.RangeActivatedPart = RangeActivatedPart.Start;
var presenter = picker.CreatePickerPresenterForTest();
picker.NotifyPickerOpenedForTest();
ShowInWindow(presenter, () =>
{
var calendar = presenter.GetVisualDescendants()
.OfType<DualMonthRangeCalendar>()
.Single();
var expectedSelectedMonth = new DateTime(2026, 9, 1);
calendar.DisplayDate.ShouldBe(displayDate);
calendar.SelectedMonth.ShouldBe(expectedSelectedMonth);
calendar.SelectedYear.ShouldBe(expectedSelectedMonth);
calendar.LastSelectedDate.ShouldBe(displayDate);
calendar.SelectedDate.ShouldBeNull();
calendar.SecondarySelectedDate.ShouldBeNull();
picker.RangeStartSelectedDate.ShouldBeNull();
picker.RangeEndSelectedDate.ShouldBeNull();
});
});
}
[Fact]
public void RangeDatePicker_Projects_Only_Valid_Endpoints_Into_Calendar()
{
RunOnUIThread(() =>
{
var invalidStart = new DateTime(2026, 7, 10);
var validEnd = new DateTime(2026, 7, 18);
var picker = new TestRangeDatePicker
{
MinDate = new DateTime(2026, 7, 15),
MaxDate = new DateTime(2026, 7, 20),
RangeStartSelectedDate = invalidStart,
RangeEndSelectedDate = validEnd
};
picker.RangeActivatedPart = RangeActivatedPart.End;
var presenter = picker.CreatePickerPresenterForTest();
picker.NotifyPickerOpenedForTest();
ShowInWindow(presenter, () =>
{
var calendar = presenter.GetVisualDescendants()
.OfType<DualMonthRangeCalendar>()
.Single();
calendar.DisplayDateStart.ShouldBe(new DateTime(2026, 7, 15));
calendar.DisplayDateEnd.ShouldBe(new DateTime(2026, 7, 20));
calendar.SelectedDate.ShouldBeNull();
calendar.SecondarySelectedDate.ShouldBe(validEnd);
picker.RangeStartSelectedDate.ShouldBe(invalidStart);
picker.RangeEndSelectedDate.ShouldBe(validEnd);
});
});
}
[Fact]
public void DatePicker_Today_And_Now_Do_Not_Select_Out_Of_Range_Values()
{
RunOnUIThread(() =>
{
var presenter = new TestDatePickerPresenter
{
MinDate = DateTime.Today.AddDays(1),
MaxDate = DateTime.Today.AddDays(10)
};
presenter.NotifyTodayButtonClickedForTest();
presenter.SelectedDateTime.ShouldBeNull();
presenter.NotifyNowButtonClickedForTest();
presenter.SelectedDateTime.ShouldBeNull();
});
}
[Fact]
public void DatePicker_Today_And_Now_Buttons_Are_Disabled_Out_Of_Range()
{
RunOnUIThread(() =>
{
var picker = new TestDatePicker
{
MinDate = DateTime.Today.AddDays(1),
MaxDate = DateTime.Today.AddDays(10)
};
var presenter = picker.CreatePickerPresenterForTest();
ShowInWindow(presenter, () =>
{
presenter.GetVisualDescendants()
.OfType<AtomUI.Desktop.Controls.Button>()
.Single(button => button.Name == "PART_TodayButton")
.IsEnabled.ShouldBeFalse();
picker.IsShowTime = true;
Dispatcher.UIThread.RunJobs();
presenter.GetVisualDescendants()
.OfType<AtomUI.Desktop.Controls.Button>()
.Single(button => button.Name == "PART_NowButton")
.IsEnabled.ShouldBeFalse();
});
});
}
[Fact]
public void Calendar_Navigation_Cannot_Move_Past_Date_Boundaries()
{
RunOnUIThread(() =>
{
var calendar = new PickerCalendar
{
DisplayDate = new DateTime(2026, 7, 1),
DisplayDateStart = new DateTime(2026, 7, 15),
DisplayDateEnd = new DateTime(2026, 7, 20)
};
ShowInWindow(calendar, () =>
{
calendar.CalendarItem.ShouldNotBeNull().PreviousMonthButton.ShouldNotBeNull().IsEnabled.ShouldBeFalse();
calendar.CalendarItem.NextMonthButton.ShouldNotBeNull().IsEnabled.ShouldBeFalse();
var displayDate = calendar.DisplayDate;
calendar.OnPreviousMonthClick();
calendar.OnNextMonthClick();
calendar.ProcessPageUpKey(false);
calendar.ProcessPageDownKey(false);
calendar.DisplayDate.ShouldBe(displayDate);
});
});
}
[Fact]
public void Calendar_Year_And_Decade_Navigation_Cannot_Move_Past_Date_Boundaries()
{
RunOnUIThread(() =>
{
var calendar = new PickerCalendar
{
DisplayDate = new DateTime(2026, 7, 1),
DisplayDateStart = new DateTime(2026, 7, 15),
DisplayDateEnd = new DateTime(2026, 7, 20)
};
ShowInWindow(calendar, () =>
{
calendar.SetCurrentValue(PickerCalendar.DisplayModeProperty, PickerCalendarMode.Year);
calendar.SelectedMonth.ShouldBe(new DateTime(2026, 7, 1));
calendar.CalendarItem.ShouldNotBeNull().PreviousButton.ShouldNotBeNull().IsEnabled.ShouldBeFalse();
calendar.CalendarItem.NextButton.ShouldNotBeNull().IsEnabled.ShouldBeFalse();
var selectedMonth = calendar.SelectedMonth;
calendar.OnPreviousClick();
calendar.OnNextClick();
calendar.ProcessPageUpKey(true);
calendar.ProcessPageDownKey(true);
calendar.SelectedMonth.ShouldBe(selectedMonth);
calendar.SetCurrentValue(PickerCalendar.DisplayModeProperty, PickerCalendarMode.Decade);
var calendarItem = calendar.CalendarItem.ShouldNotBeNull();
calendarItem.PreviousButton.ShouldNotBeNull().IsEnabled.ShouldBeFalse();
calendarItem.NextButton.ShouldNotBeNull().IsEnabled.ShouldBeFalse();
var selectedYear = calendar.SelectedYear;
calendar.OnPreviousClick();
calendar.OnNextClick();
calendar.ProcessPageUpKey(true);
calendar.ProcessPageDownKey(true);
calendar.SelectedYear.ShouldBe(selectedYear);
});
});
}
[Fact]
public void RangeDatePicker_Open_End_Part_Uses_Selected_End_Before_PickerDisplayDate()
{
RunOnUIThread(() =>
{
var selectedEnd = new DateTime(2026, 2, 11);
var picker = new TestRangeDatePicker
{
PickerDisplayDate = new DateTime(2027, 9, 23),
RangeStartSelectedDate = new DateTime(2026, 1, 12),
RangeEndSelectedDate = selectedEnd
};
picker.RangeActivatedPart = RangeActivatedPart.End;
var presenter = picker.CreatePickerPresenterForTest();
picker.NotifyPickerOpenedForTest();
ShowInWindow(presenter, () =>
{
var calendar = presenter.GetVisualDescendants()
.OfType<DualMonthRangeCalendar>()
.Single();
calendar.IsSelectRangeStart.ShouldBeFalse();
calendar.DisplayDate.ShouldBe(new DateTime(2026, 1, 11));
calendar.SecondarySelectedDate.ShouldBe(selectedEnd);
picker.RangeEndSelectedDate.ShouldBe(selectedEnd);
});
});
}
[Fact]
public void RangeDatePicker_Month_End_Hover_Renders_Preview_Range_From_Selected_Start()
{
RunOnUIThread(() =>
{
var picker = new TestRangeDatePicker
{
PickerMode = DatePickerMode.Month,
RangeStartSelectedDate = new DateTime(2026, 5, 1),
RangeEndSelectedDate = null
};
picker.RangeActivatedPart = RangeActivatedPart.End;
var presenter = picker.CreatePickerPresenterForTest();
picker.NotifyPickerOpenedForTest();
ShowInWindow(presenter, () =>
{
var calendar = presenter.GetVisualDescendants()
.OfType<DualMonthRangeCalendar>()
.Single();
calendar.SetCurrentValue(PickerCalendar.DisplayDateProperty, new DateTime(2026, 1, 1));
Dispatcher.UIThread.RunJobs();
var item = calendar.CalendarItem.ShouldBeOfType<PickerDualMonthCalendarItem>();
var secondaryYearView = FindNamedGrid(item, "PART_SecondaryYearView");
var hoverButton = FindCalendarButton(secondaryYearView, new DateTime(2027, 8, 1));
item.HandleMonthMouseEntered(hoverButton, null!);
Dispatcher.UIThread.RunJobs();
var state = calendar.SyncAndGetCurrentViewState();
state.RangeSelection.TryGetPreviewRange(out var previewStart, out var previewEnd).ShouldBeTrue();
previewStart.ShouldBe(new DateTime(2026, 5, 1));
previewEnd.ShouldBe(new DateTime(2027, 8, 1));
IsRangePreviewStart(FindCalendarButton(item.YearView.ShouldNotBeNull(), new DateTime(2026, 5, 1))).ShouldBeTrue();
var primaryMiddle = FindCalendarButton(item.YearView, new DateTime(2026, 6, 1));
var secondaryMiddle = FindCalendarButton(secondaryYearView, new DateTime(2027, 7, 1));
IsRangePreviewMiddle(primaryMiddle).ShouldBeTrue();
IsRangePreviewMiddle(secondaryMiddle).ShouldBeTrue();
FindTemplateBorder(primaryMiddle, "RangeIndicator").IsVisible.ShouldBeTrue();
FindTemplateBorder(secondaryMiddle, "RangeIndicator").IsVisible.ShouldBeTrue();
IsRangePreviewEnd(hoverButton).ShouldBeTrue();
});
});
}
[Fact]
public void RangeDatePicker_Month_Reopen_Complete_Range_Anchors_Dual_Panel_To_Active_End()
{
RunOnUIThread(() =>
{
var startDate = new DateTime(2026, 5, 1);
var endDate = new DateTime(2028, 9, 1);
var picker = new TestRangeDatePicker
{
PickerMode = DatePickerMode.Month,
RangeStartSelectedDate = startDate,
RangeEndSelectedDate = endDate
};
picker.RangeActivatedPart = RangeActivatedPart.End;
var presenter = picker.CreatePickerPresenterForTest();
ShowInWindow(presenter, () =>
{
var calendar = presenter.GetVisualDescendants()
.OfType<DualMonthRangeCalendar>()
.Single();
calendar.SetCurrentValue(PickerCalendar.DisplayDateProperty, endDate);
calendar.SelectedMonth = endDate;
Dispatcher.UIThread.RunJobs();
picker.NotifyPickerOpenedForTest();
Dispatcher.UIThread.RunJobs();
calendar.DisplayDate.Year.ShouldBe(2027);
calendar.SelectedMonth.Year.ShouldBe(2027);
var item = calendar.CalendarItem.ShouldBeOfType<PickerDualMonthCalendarItem>();
var secondaryYearView = FindNamedGrid(item, "PART_SecondaryYearView");
FindCalendarButton(item.YearView.ShouldNotBeNull(), new DateTime(2027, 1, 1));
FindCalendarButton(secondaryYearView, new DateTime(2028, 1, 1));
var endButton = FindCalendarButton(secondaryYearView, endDate);
IsRangeEnd(endButton).ShouldBeTrue();
IsRangePreviewEnd(endButton).ShouldBeFalse();
});
});
}
[Fact]
public void RangeDatePicker_Month_Reopen_Complete_Range_Anchors_Dual_Panel_To_Active_Start()
{
RunOnUIThread(() =>
{
var startDate = new DateTime(2026, 5, 1);
var endDate = new DateTime(2028, 9, 1);
var picker = new TestRangeDatePicker
{
PickerMode = DatePickerMode.Month,
RangeStartSelectedDate = startDate,
RangeEndSelectedDate = endDate
};
picker.RangeActivatedPart = RangeActivatedPart.Start;
var presenter = picker.CreatePickerPresenterForTest();
ShowInWindow(presenter, () =>
{
var calendar = presenter.GetVisualDescendants()
.OfType<DualMonthRangeCalendar>()
.Single();
calendar.SetCurrentValue(PickerCalendar.DisplayDateProperty, endDate);
calendar.SelectedMonth = endDate;
Dispatcher.UIThread.RunJobs();
picker.NotifyPickerOpenedForTest();
Dispatcher.UIThread.RunJobs();
calendar.DisplayDate.Year.ShouldBe(2026);
calendar.SelectedMonth.Year.ShouldBe(2026);
var item = calendar.CalendarItem.ShouldBeOfType<PickerDualMonthCalendarItem>();
var startButton = FindCalendarButton(item.YearView.ShouldNotBeNull(), startDate);
var secondaryYearView = FindNamedGrid(item, "PART_SecondaryYearView");
FindCalendarButton(secondaryYearView, new DateTime(2027, 1, 1));
IsRangeStart(startButton).ShouldBeTrue();
IsRangePreviewStart(startButton).ShouldBeFalse();
});
});
}
[Theory]
[InlineData(DatePickerMode.Month, 2027, 6, 1)]
[InlineData(DatePickerMode.Quarter, 2027, 4, 1)]
[InlineData(DatePickerMode.Year, 2027, 1, 1)]
public void DualMonthRangeCalendar_PickerUnit_Hover_Selected_Endpoint_Does_Not_Render_Degenerate_Preview(
DatePickerMode pickerMode,
int selectedYear,
int selectedMonth,
int selectedDay)
{
RunOnUIThread(() =>
{
var selectedDate = new DateTime(selectedYear, selectedMonth, selectedDay);
var calendar = new DualMonthRangeCalendar
{
PickerMode = pickerMode,
DisplayDate = selectedDate,
SelectedDate = selectedDate,
HoverDateTime = selectedDate,
IsSelectRangeStart = false
};
ShowInWindow(calendar, () =>
{
var state = calendar.SyncAndGetCurrentViewState();
state.RangeSelection.TryGetPreviewRange(out _, out _).ShouldBeFalse();
var item = calendar.CalendarItem.ShouldBeOfType<PickerDualMonthCalendarItem>();
var button = FindCalendarButton(item.YearView.ShouldNotBeNull(), selectedDate);
IsSelected(button).ShouldBeTrue();
IsRangePreviewStart(button).ShouldBeFalse();
IsRangePreviewEnd(button).ShouldBeFalse();
IsRangePreviewMiddle(button).ShouldBeFalse();
FindTemplateBorder(button, "RangeIndicator").IsVisible.ShouldBeFalse();
FindTemplateBorder(button, "RangeStartIndicator").IsVisible.ShouldBeFalse();
FindTemplateBorder(button, "RangeEndIndicator").IsVisible.ShouldBeFalse();
});
});
}
[Fact]
public void TimedRangeDatePicker_Open_End_Part_Does_Not_Let_Start_Date_Rewind_Calendar_DisplayDate()
{
RunOnUIThread(() =>
{
var picker = new TestRangeDatePicker
{
IsShowTime = true,
RangeStartSelectedDate = new DateTime(2024, 1, 12, 10, 30, 0)
};
picker.RangeActivatedPart = RangeActivatedPart.End;
var presenter = picker.CreatePickerPresenterForTest();
picker.NotifyPickerOpenedForTest();
ShowInWindow(presenter, () =>
{
var calendar = presenter.GetVisualDescendants()
.OfType<RangeCalendar>()
.Single();
calendar.IsSelectRangeStart.ShouldBeFalse();
DateTimeHelper.CompareYearMonth(calendar.DisplayDate, picker.RangeStartSelectedDate.Value)
.ShouldNotBe(0);
});
});
}
[Fact]
public void Calendar_FirstDayOfWeek_Change_Rebuilds_Week_Title_Order()
{
RunOnUIThread(() =>
{
var calendar = new PickerCalendar
{
DisplayDate = new DateTime(2026, 6, 1),
FirstDayOfWeek = DayOfWeek.Monday
};
ShowInWindow(calendar, () =>
{
var titles = calendar.CalendarItem.ShouldNotBeNull()
.MonthView.ShouldNotBeNull()
.Children
.Take(7)
.Select(child => child.DataContext?.ToString())
.ToArray();
titles[0].ShouldNotBeNullOrWhiteSpace();
calendar.FirstDayOfWeek = DayOfWeek.Sunday;
Dispatcher.UIThread.RunJobs();
var updatedTitles = calendar.CalendarItem.ShouldNotBeNull()
.MonthView.ShouldNotBeNull()
.Children
.Take(7)
.Select(child => child.DataContext?.ToString())
.ToArray();
updatedTitles.ShouldNotBe(titles);
});
});
}
[Fact]
public void CalendarItem_LanguageManager_Subscription_Follows_VisualTree_Lifecycle()
{
RunOnUIThread(() =>
{
var languageManager = Application.Current!.GetLanguageManager().ShouldNotBeNull();
var originalLanguage = languageManager.Current.CurrentLanguage;
languageManager.ChangeLanguage(LanguageTags.EnUS);
var calendar = new PickerCalendar
{
DisplayDate = new DateTime(2026, 6, 1)
};
var window = new AvaloniaWindow
{
Width = 420,
Height = 360,
Content = calendar
};
try
{
window.Show();
Dispatcher.UIThread.RunJobs();
languageManager.ChangeLanguage(LanguageTags.ZhCN);
Dispatcher.UIThread.RunJobs();
var activeCulture = calendar.SyncAndGetCurrentViewState().Culture;
activeCulture.ShortDatePattern.ShouldBe(
languageManager.Current.FormattingCulture.DateTimeFormat.ShortDatePattern);
window.Content = null;
Dispatcher.UIThread.RunJobs();
languageManager.ChangeLanguage(LanguageTags.EnUS);
Dispatcher.UIThread.RunJobs();
calendar.SyncAndGetCurrentViewState().Culture.ShouldBeSameAs(activeCulture);
window.Content = calendar;
Dispatcher.UIThread.RunJobs();
calendar.SyncAndGetCurrentViewState().Culture.ShortDatePattern.ShouldBe(
languageManager.Current.FormattingCulture.DateTimeFormat.ShortDatePattern);
}
finally
{
window.Close();
languageManager.ChangeLanguage(originalLanguage);
Dispatcher.UIThread.RunJobs();
}
});
}
[Fact]
public void DualMonthRangeCalendar_Secondary_Panel_Uses_Range_Highlight()
{
RunOnUIThread(() =>
{
var calendar = new DualMonthRangeCalendar
{
DisplayDate = new DateTime(2026, 6, 1),
SelectedDate = new DateTime(2026, 6, 28),
SecondarySelectedDate = new DateTime(2026, 7, 3)
};
ShowInWindow(calendar, () =>
{
var item = calendar.CalendarItem.ShouldBeOfType<PickerDualMonthCalendarItem>();
var secondaryButton = item.SecondaryMonthView.ShouldNotBeNull()
.Children
.OfType<PickerCalendarDayButton>()
.Single(button => button.DataContext is DateTime day &&
DateTimeHelper.CompareDays(day, new DateTime(2026, 7, 3)) == 0);
IsRangeEnd(secondaryButton).ShouldBeTrue();
});
});
}
[Fact]
public void DualMonthRangeCalendar_Month_Picker_Uses_Two_Year_Panels()
{
RunOnUIThread(() =>
{
var calendar = new DualMonthRangeCalendar
{
PickerMode = DatePickerMode.Month,
DisplayDate = new DateTime(2026, 4, 1)
};
ShowInWindow(calendar, () =>
{
var item = calendar.CalendarItem.ShouldBeOfType<PickerDualMonthCalendarItem>();
var secondaryYearView = FindNamedGrid(item, "PART_SecondaryYearView");
calendar.DisplayMode.ShouldBe(PickerCalendarMode.Year);
item.MonthViewLayout.ShouldNotBeNull().IsVisible.ShouldBeFalse();
item.YearViewLayout.ShouldNotBeNull().IsVisible.ShouldBeTrue();
item.YearView.ShouldNotBeNull().IsVisible.ShouldBeTrue();
secondaryYearView.IsVisible.ShouldBeTrue();
GetCalendarButtonDates(item.YearView).Where(date => date.Year == 2026).Count().ShouldBe(12);
GetCalendarButtonDates(secondaryYearView).Where(date => date.Year == 2027).Count().ShouldBe(12);
item.YearView.RowDefinitions.Count.ShouldBe(4);
item.YearView.ColumnDefinitions.Count.ShouldBe(3);
secondaryYearView.RowDefinitions.Count.ShouldBe(4);
secondaryYearView.ColumnDefinitions.Count.ShouldBe(3);
});
});
}
[Fact]
public void DualMonthRangeCalendar_Month_Hover_End_Renders_Range_Preview_In_Year_Panels()
{
RunOnUIThread(() =>
{
var calendar = new DualMonthRangeCalendar
{
PickerMode = DatePickerMode.Month,
DisplayDate = new DateTime(2026, 4, 1),
SelectedDate = new DateTime(2026, 7, 1),
HoverDateTime = new DateTime(2027, 8, 1),
IsSelectRangeStart = false
};
ShowInWindow(calendar, () =>
{
var item = calendar.CalendarItem.ShouldBeOfType<PickerDualMonthCalendarItem>();
var primaryYearView = item.YearView.ShouldNotBeNull();
var secondaryYearView = FindNamedGrid(item, "PART_SecondaryYearView");
var start = FindCalendarButton(primaryYearView, new DateTime(2026, 7, 1));
var primaryMiddle = FindCalendarButton(primaryYearView, new DateTime(2026, 8, 1));
var secondaryMiddle = FindCalendarButton(secondaryYearView, new DateTime(2027, 7, 1));
var end = FindCalendarButton(secondaryYearView, new DateTime(2027, 8, 1));
var afterEnd = FindCalendarButton(secondaryYearView, new DateTime(2027, 9, 1));
IsSelected(start).ShouldBeFalse();
IsRangePreviewStart(start).ShouldBeTrue();
IsRangePreviewMiddle(primaryMiddle).ShouldBeTrue();
IsRangePreviewMiddle(secondaryMiddle).ShouldBeTrue();
IsSelected(end).ShouldBeFalse();
IsRangePreviewEnd(end).ShouldBeTrue();
IsRangePreviewMiddle(afterEnd).ShouldBeFalse();
});
});
}
[Fact]
public void DualMonthRangeCalendar_Quarter_Picker_Uses_Two_Compact_Quarter_Panels()
{
RunOnUIThread(() =>
{
var calendar = new DualMonthRangeCalendar
{
PickerMode = DatePickerMode.Quarter,
DisplayDate = new DateTime(2026, 4, 1)
};
ShowInWindow(calendar, () =>
{
var item = calendar.CalendarItem.ShouldBeOfType<PickerDualMonthCalendarItem>();
var secondaryYearView = FindNamedGrid(item, "PART_SecondaryYearView");
calendar.DisplayMode.ShouldBe(PickerCalendarMode.Year);
item.YearView.ShouldNotBeNull().IsVisible.ShouldBeTrue();
secondaryYearView.IsVisible.ShouldBeTrue();
item.YearView.RowDefinitions.Count.ShouldBe(1);
item.YearView.ColumnDefinitions.Count.ShouldBe(4);
secondaryYearView.RowDefinitions.Count.ShouldBe(1);
secondaryYearView.ColumnDefinitions.Count.ShouldBe(4);
GetVisibleCalendarButtonTexts(item.YearView).ShouldBe(new[] { "Q1", "Q2", "Q3", "Q4" });
GetVisibleCalendarButtonTexts(secondaryYearView).ShouldBe(new[] { "Q1", "Q2", "Q3", "Q4" });
GetCalendarButtonDates(item.YearView).ShouldAllBe(date => date.Year == 2026);
GetCalendarButtonDates(secondaryYearView).ShouldAllBe(date => date.Year == 2027);
});
});
}
[Fact]
public void DualMonthRangeCalendar_Quarter_Hover_End_Renders_Range_Preview_In_Quarter_Panels()
{
RunOnUIThread(() =>
{
var calendar = new DualMonthRangeCalendar
{
PickerMode = DatePickerMode.Quarter,
DisplayDate = new DateTime(2026, 4, 1),
SelectedDate = new DateTime(2026, 4, 1),
HoverDateTime = new DateTime(2027, 7, 1),
IsSelectRangeStart = false
};
ShowInWindow(calendar, () =>
{
var item = calendar.CalendarItem.ShouldBeOfType<PickerDualMonthCalendarItem>();
var primaryYearView = item.YearView.ShouldNotBeNull();
var secondaryYearView = FindNamedGrid(item, "PART_SecondaryYearView");
var start = FindCalendarButton(primaryYearView, new DateTime(2026, 4, 1));
var primaryMiddle = FindCalendarButton(primaryYearView, new DateTime(2026, 7, 1));
var secondaryMiddle = FindCalendarButton(secondaryYearView, new DateTime(2027, 4, 1));
var end = FindCalendarButton(secondaryYearView, new DateTime(2027, 7, 1));
var afterEnd = FindCalendarButton(secondaryYearView, new DateTime(2027, 10, 1));
IsSelected(start).ShouldBeFalse();
IsRangePreviewStart(start).ShouldBeTrue();
IsRangePreviewMiddle(primaryMiddle).ShouldBeTrue();
IsRangePreviewMiddle(secondaryMiddle).ShouldBeTrue();
IsSelected(end).ShouldBeFalse();
IsRangePreviewEnd(end).ShouldBeTrue();
IsRangePreviewMiddle(afterEnd).ShouldBeFalse();
});
});
}
[Fact]
public void DualMonthRangeCalendar_Year_Picker_Uses_Two_Decade_Panels()
{
RunOnUIThread(() =>
{
var calendar = new DualMonthRangeCalendar
{
PickerMode = DatePickerMode.Year,
DisplayDate = new DateTime(2026, 4, 1)
};
ShowInWindow(calendar, () =>
{
var item = calendar.CalendarItem.ShouldBeOfType<PickerDualMonthCalendarItem>();
var secondaryYearView = FindNamedGrid(item, "PART_SecondaryYearView");
calendar.DisplayMode.ShouldBe(PickerCalendarMode.Decade);
item.YearView.ShouldNotBeNull().IsVisible.ShouldBeTrue();
secondaryYearView.IsVisible.ShouldBeTrue();
GetCalendarButtonDates(item.YearView).Any(date => date.Year == 2020).ShouldBeTrue();
GetCalendarButtonDates(item.YearView).Any(date => date.Year == 2029).ShouldBeTrue();
GetCalendarButtonDates(secondaryYearView).Any(date => date.Year == 2030).ShouldBeTrue();
GetCalendarButtonDates(secondaryYearView).Any(date => date.Year == 2039).ShouldBeTrue();
});
});
}
[Fact]
public void DualMonthRangeCalendar_Year_Hover_End_Renders_Range_Preview_In_Decade_Panels()
{
RunOnUIThread(() =>
{
var calendar = new DualMonthRangeCalendar
{
PickerMode = DatePickerMode.Year,
DisplayDate = new DateTime(2026, 1, 1),
SelectedDate = new DateTime(2026, 1, 1),
HoverDateTime = new DateTime(2032, 1, 1),
IsSelectRangeStart = false
};
ShowInWindow(calendar, () =>
{
var item = calendar.CalendarItem.ShouldBeOfType<PickerDualMonthCalendarItem>();
var primaryYearView = item.YearView.ShouldNotBeNull();
var secondaryYearView = FindNamedGrid(item, "PART_SecondaryYearView");
var start = FindCalendarButton(primaryYearView, new DateTime(2026, 1, 1));
var primaryMiddle = FindCalendarButton(primaryYearView, new DateTime(2027, 1, 1));
var secondaryMiddle = FindCalendarButton(secondaryYearView, new DateTime(2031, 1, 1));
var end = FindCalendarButton(secondaryYearView, new DateTime(2032, 1, 1));
var afterEnd = FindCalendarButton(secondaryYearView, new DateTime(2033, 1, 1));
IsSelected(start).ShouldBeFalse();
IsRangePreviewStart(start).ShouldBeTrue();
IsRangePreviewMiddle(primaryMiddle).ShouldBeTrue();
IsRangePreviewMiddle(secondaryMiddle).ShouldBeTrue();
IsSelected(end).ShouldBeFalse();
IsRangePreviewEnd(end).ShouldBeTrue();
IsRangePreviewMiddle(afterEnd).ShouldBeFalse();
});
});
}
[Theory]
[InlineData(DatePickerMode.Month, PickerCalendarMode.Year)]
[InlineData(DatePickerMode.Quarter, PickerCalendarMode.Year)]
[InlineData(DatePickerMode.Year, PickerCalendarMode.Decade)]
public void DualMonthRangeCalendar_PickerMode_Runtime_Change_Uses_Only_Target_Dual_Panel(
DatePickerMode pickerMode,
PickerCalendarMode expectedDisplayMode)
{
RunOnUIThread(() =>
{
var calendar = new DualMonthRangeCalendar
{
DisplayDate = new DateTime(2026, 7, 1)
};
ShowInWindow(calendar, () =>
{
var item = calendar.CalendarItem.ShouldBeOfType<PickerDualMonthCalendarItem>();
AssertDualMonthPanelVisibility(item, monthVisible: true);
calendar.PickerMode = pickerMode;
Dispatcher.UIThread.RunJobs();
calendar.DisplayMode.ShouldBe(expectedDisplayMode);
AssertDualMonthPanelVisibility(item, monthVisible: false);
calendar.PickerMode = DatePickerMode.Week;
Dispatcher.UIThread.RunJobs();
calendar.DisplayMode.ShouldBe(PickerCalendarMode.Month);
AssertDualMonthPanelVisibility(item, monthVisible: true);
});
});
}
[Theory]
[InlineData(DatePickerMode.Month, PickerCalendarMode.Year)]
[InlineData(DatePickerMode.Quarter, PickerCalendarMode.Year)]
[InlineData(DatePickerMode.Year, PickerCalendarMode.Decade)]
public void Calendar_PickerMode_Runtime_Change_Uses_Only_Target_Panel(
DatePickerMode pickerMode,
PickerCalendarMode expectedDisplayMode)
{
RunOnUIThread(() =>
{
var calendar = new PickerCalendar
{
DisplayDate = new DateTime(2026, 7, 1)
};
ShowInWindow(calendar, () =>
{
var item = calendar.CalendarItem.ShouldBeOfType<PickerCalendarItem>();
AssertCalendarPanelVisibility(item, monthVisible: true);
calendar.PickerMode = pickerMode;
Dispatcher.UIThread.RunJobs();
calendar.DisplayMode.ShouldBe(expectedDisplayMode);
AssertCalendarPanelVisibility(item, monthVisible: false);
calendar.PickerMode = DatePickerMode.Week;
Dispatcher.UIThread.RunJobs();
calendar.DisplayMode.ShouldBe(PickerCalendarMode.Month);
AssertCalendarPanelVisibility(item, monthVisible: true);
});
});
}
[Fact]
public void DualMonthRangeCalendar_Hover_End_Renders_Continuous_Preview_Range_In_Secondary_Panel()
{
RunOnUIThread(() =>
{
var calendar = new DualMonthRangeCalendar
{
DisplayDate = new DateTime(2026, 7, 1),
SelectedDate = new DateTime(2026, 7, 17),
HoverDateTime = new DateTime(2026, 8, 19),
IsSelectRangeStart = false
};
ShowInWindow(calendar, () =>
{
var middleButton = FindSecondaryDayButton(calendar, new DateTime(2026, 8, 18));
var endButton = FindSecondaryDayButton(calendar, new DateTime(2026, 8, 19));
IsRangePreviewMiddle(middleButton).ShouldBeTrue();
IsSelected(endButton).ShouldBeFalse();
IsRangeEnd(endButton).ShouldBeFalse();
IsRangePreviewEnd(endButton).ShouldBeTrue();
var endIndicator = FindTemplateBorder(endButton, "RangeEndIndicator");
endIndicator.Bounds.Width.ShouldBe(endButton.Bounds.Width / 2, 0.5);
});
});
}
[Fact]
public void DualMonthRangeCalendar_Week_Hover_End_Renders_Row_Preview_In_Secondary_Panel()
{
RunOnUIThread(() =>
{
var calendar = new DualMonthRangeCalendar
{
PickerMode = DatePickerMode.Week,
DisplayDate = new DateTime(2026, 7, 1),
SelectedDate = new DateTime(2026, 7, 13),
HoverDateTime = new DateTime(2026, 8, 10),
IsSelectRangeStart = false
};
ShowInWindow(calendar, () =>
{
var rangeMiddleStart = FindSecondaryDayButton(calendar, new DateTime(2026, 8, 3));
var rangeMiddleEnd = FindSecondaryDayButton(calendar, new DateTime(2026, 8, 9));
var previewStart = FindSecondaryDayButton(calendar, new DateTime(2026, 8, 10));
var previewMiddle = FindSecondaryDayButton(calendar, new DateTime(2026, 8, 13));
var previewEnd = FindSecondaryDayButton(calendar, new DateTime(2026, 8, 16));
IsWeekRangeMiddle(rangeMiddleStart).ShouldBeTrue();
IsWeekRangeEnd(rangeMiddleEnd).ShouldBeTrue();
IsSelected(previewStart).ShouldBeFalse();
IsWeekSelectionMiddle(previewStart).ShouldBeTrue();
IsWeekSelectionMiddle(previewMiddle).ShouldBeTrue();
IsWeekSelectionEnd(previewEnd).ShouldBeTrue();
});
});
}
private static bool IsSelected(PickerCalendarDayButton button)
{
return button.Classes.Contains(":selected");
}
private static bool IsRangeEnd(PickerCalendarDayButton button)
{
return button.Classes.Contains(":range-end");
}
private static bool IsRangeStart(PickerCalendarDayButton button)
{
return button.Classes.Contains(":range-start");
}
private static bool IsRangeMiddle(PickerCalendarDayButton button)
{
return button.Classes.Contains(":range-middle");
}
private static bool IsRangePreviewEnd(PickerCalendarDayButton button)
{
return button.Classes.Contains(":range-preview-end");
}
private static bool IsRangePreviewStart(PickerCalendarDayButton button)
{
return button.Classes.Contains(":range-preview-start");
}
private static bool IsRangePreviewMiddle(PickerCalendarDayButton button)
{
return button.Classes.Contains(":range-preview-middle");
}
private static bool IsWeekSelectionMiddle(PickerCalendarDayButton button)
{
return button.Classes.Contains(":week-selection-middle");
}
private static bool IsWeekSelectionEnd(PickerCalendarDayButton button)
{
return button.Classes.Contains(":week-selection-end");
}
private static bool IsWeekRangeMiddle(PickerCalendarDayButton button)
{
return button.Classes.Contains(":week-range-middle");
}
private static bool IsWeekRangeEnd(PickerCalendarDayButton button)
{
return button.Classes.Contains(":week-range-end");
}
private static bool IsSelected(PickerCalendarButton button)
{
return button.Classes.Contains(":selected");
}
private static bool IsRangeEnd(PickerCalendarButton button)
{
return button.Classes.Contains(":range-end");
}
private static bool IsRangeStart(PickerCalendarButton button)
{
return button.Classes.Contains(":range-start");
}
private static bool IsRangePreviewEnd(PickerCalendarButton button)
{
return button.Classes.Contains(":range-preview-end");
}
private static bool IsRangePreviewStart(PickerCalendarButton button)
{
return button.Classes.Contains(":range-preview-start");
}
private static bool IsRangePreviewMiddle(PickerCalendarButton button)
{
return button.Classes.Contains(":range-preview-middle");
}
private static void AssertTemplateParts(Type controlType, IReadOnlyDictionary<string, Type> expectedParts)
{
var parts = controlType.GetCustomAttributes(typeof(TemplatePartAttribute), false)
.Cast<TemplatePartAttribute>()
.ToDictionary(part => part.Name, part => part.Type);
foreach (var (name, expectedType) in expectedParts)
{
parts.TryGetValue(name, out var actualType).ShouldBeTrue(name);
actualType.ShouldBe(expectedType);
}
}
private static PickerCalendarDayButton FindDayButton(PickerCalendar calendar, DateTime date)
{
var buttons = calendar.CalendarItem.ShouldNotBeNull()
.MonthView.ShouldNotBeNull()
.Children
.OfType<PickerCalendarDayButton>()
.ToArray();
var matchedButton = buttons.SingleOrDefault(button =>
button.DataContext is DateTime buttonDate &&
DateTimeHelper.CompareDays(buttonDate, date) == 0);
if (matchedButton is not null)
{
return matchedButton;
}
var actualDates = string.Join(
", ",
buttons.Select(button => button.DataContext)
.OfType<DateTime>()
.Select(buttonDate => buttonDate.ToString("yyyy-MM-dd")));
var monthView = calendar.CalendarItem.ShouldNotBeNull().MonthView.ShouldNotBeNull();
var childTypes = string.Join(
", ",
monthView.Children.Select(child => child.GetType().FullName));
throw new InvalidOperationException(
$"Date {date:yyyy-MM-dd} was not generated. ChildCount: {monthView.Children.Count}; ButtonCount: {buttons.Length}; Actual dates: {actualDates}; ChildTypes: {childTypes}");
}
private static PickerCalendarDayButton FindSecondaryDayButton(DualMonthRangeCalendar calendar, DateTime date)
{
var item = calendar.CalendarItem.ShouldBeOfType<PickerDualMonthCalendarItem>();
var buttons = item.SecondaryMonthView.ShouldNotBeNull()
.Children
.OfType<PickerCalendarDayButton>()
.ToArray();
return buttons.Single(button =>
!button.IsWeekNumber &&
button.DataContext is DateTime buttonDate &&
DateTimeHelper.CompareDays(buttonDate, date) == 0);
}
private static AvaloniaGrid FindNamedGrid(Control control, string name)
{
return control.GetVisualDescendants()
.OfType<AvaloniaGrid>()
.Single(grid => grid.Name == name);
}
private static DateTime[] GetCalendarButtonDates(AvaloniaGrid grid)
{
return grid.Children
.OfType<PickerCalendarButton>()
.Where(button => button.IsVisible &&
button.Opacity > 0 &&
button.DataContext is DateTime)
.Select(button => (DateTime)button.DataContext!)
.ToArray();
}
private static string[] GetVisibleCalendarButtonTexts(AvaloniaGrid grid)
{
return grid.Children
.OfType<PickerCalendarButton>()
.Where(button => button.IsVisible && button.Opacity > 0)
.Select(button => button.Content?.ToString() ?? string.Empty)
.ToArray();
}
private static PickerCalendarButton FindCalendarButton(AvaloniaGrid grid, DateTime date)
{
return grid.Children
.OfType<PickerCalendarButton>()
.Single(button => button.DataContext is DateTime buttonDate &&
DateTimeHelper.CompareDays(buttonDate, date) == 0);
}
private static void AssertDualMonthPanelVisibility(PickerDualMonthCalendarItem item, bool monthVisible)
{
AssertCalendarPanelVisibility(item, monthVisible);
item.YearViewLayout.ShouldNotBeNull().IsVisible.ShouldBe(!monthVisible);
FindNamedGrid(item, "PART_SecondaryYearView").IsVisible.ShouldBe(!monthVisible);
item.SecondaryMonthView.ShouldNotBeNull().IsVisible.ShouldBe(monthVisible);
}
private static void AssertCalendarPanelVisibility(PickerCalendarItem item, bool monthVisible)
{
item.MonthViewLayout.ShouldNotBeNull().IsVisible.ShouldBe(monthVisible);
item.MonthView.ShouldNotBeNull().IsVisible.ShouldBe(monthVisible);
item.YearView.ShouldNotBeNull().IsVisible.ShouldBe(!monthVisible);
}
private static Border FindTemplateBorder(Control control, string name)
{
control.ApplyTemplate();
Dispatcher.UIThread.RunJobs();
return control.GetVisualDescendants()
.OfType<Border>()
.Single(border => border.Name == name);
}
private static void ShowInWindow(Control content, Action assertion)
{
var window = new AvaloniaWindow
{
Width = 420,
Height = 360,
Content = content
};
try
{
window.Show();
Dispatcher.UIThread.RunJobs();
assertion();
}
finally
{
window.Close();
}
}
private static void RunOnUIThread(Action action)
{
Dispatcher.UIThread.Invoke(action);
}
private static string ReadRepoFile(string relativePath)
{
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory is not null)
{
var candidate = Path.Combine(directory.FullName, relativePath);
if (File.Exists(candidate))
{
return File.ReadAllText(candidate);
}
directory = directory.Parent;
}
throw new FileNotFoundException($"Could not find repository file: {relativePath}");
}
private sealed class TestCalendarItem : PickerCalendarItem
{
public void ClearGeneratedMonthViewForTest(AvaloniaGrid monthView)
{
ClearGeneratedMonthView(monthView);
}
public void HandleMonthCalendarButtonMouseUpForTest(PickerCalendarButton button)
{
HandleMonthCalendarButtonMouseUp(button, null!);
}
}
private sealed class TestRangeCalendarItem : RangeCalendarItem
{
public void HandleMonthMouseEnteredForTest(PickerCalendarButton button)
{
HandleMonthMouseEntered(button, null!);
}
}
private sealed class TestDatePicker : DatePicker
{
public DatePickerPresenter CreatePickerPresenterForTest()
{
var presenter = CreatePickerPresenter().ShouldBeAssignableTo<DatePickerPresenter>();
NotifyPickerPresenterCreated(presenter);
return presenter;
}
public void NotifyPickerOpenedForTest()
{
NotifyPickerOpened();
}
}
private sealed class TestDatePickerPresenter : DatePickerPresenter
{
public void NotifyTodayButtonClickedForTest()
{
NotifyTodayButtonClicked();
}
public void NotifyNowButtonClickedForTest()
{
NotifyNowButtonClicked();
}
}
private sealed class TestRangeDatePicker : RangeDatePicker
{
public RangeDatePickerPresenter CreatePickerPresenterForTest()
{
var presenter = CreatePickerPresenter().ShouldBeAssignableTo<RangeDatePickerPresenter>();
NotifyPickerPresenterCreated(presenter);
return presenter;
}
public void NotifyPickerOpenedForTest()
{
NotifyPickerOpened();
}
}
private sealed class TestRangeDatePickerPresenter : RangeDatePickerPresenter
{
public void NotifyNowButtonClickedForTest()
{
NotifyNowButtonClicked();
}
public void NotifyTodayButtonClickedForTest()
{
NotifyTodayButtonClicked();
}
}
private sealed class TestTimedRangeDatePickerPresenter : TimedRangeDatePickerPresenter
{
public void NotifyNowButtonClickedForTest()
{
NotifyNowButtonClicked();
}
}
}