Merge pull request #575 from irihitech/commitinput
Date/Time picker tests and cleanup.
This commit is contained in:
@@ -8,6 +8,10 @@
|
|||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Ursa.Demo.Pages.DatePickerDemo">
|
x:Class="Ursa.Demo.Pages.DatePickerDemo">
|
||||||
<StackPanel Margin="20" HorizontalAlignment="Left">
|
<StackPanel Margin="20" HorizontalAlignment="Left">
|
||||||
|
<u:Form>
|
||||||
|
<u:DatePicker Width="200" u:FormItem.Label="_Test"/>
|
||||||
|
<TextBox Width="200" u:FormItem.Label="_West"/>
|
||||||
|
</u:Form>
|
||||||
<u:CalendarView DateSelected="CalendarView_OnOnDateSelected" DatePreviewed="CalendarView_OnOnDatePreviewed"/>
|
<u:CalendarView DateSelected="CalendarView_OnOnDateSelected" DatePreviewed="CalendarView_OnOnDatePreviewed"/>
|
||||||
<TextBlock Text="{Binding #singlePicker.SelectedDate}" ></TextBlock>
|
<TextBlock Text="{Binding #singlePicker.SelectedDate}" ></TextBlock>
|
||||||
<u:DatePicker Name="singlePicker" Width="200" Classes="ClearButton" />
|
<u:DatePicker Name="singlePicker" Width="200" Classes="ClearButton" />
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
using Avalonia.Metadata;
|
using Avalonia.Metadata;
|
||||||
|
|
||||||
|
[assembly:InternalsVisibleTo("HeadlessTest.Ursa")]
|
||||||
|
[assembly:InternalsVisibleTo("Test.Ursa")]
|
||||||
[assembly:XmlnsPrefix("https://irihi.tech/ursa", "u")]
|
[assembly:XmlnsPrefix("https://irihi.tech/ursa", "u")]
|
||||||
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa")]
|
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa")]
|
||||||
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Controls")]
|
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Controls")]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace Ursa.Controls;
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
public sealed class CalendarContext(int? year = null, int? month = null, int? startYear = null, int? endYear = null): IComparable<CalendarContext>
|
internal sealed class CalendarContext(int? year = null, int? month = null, int? startYear = null, int? endYear = null): IComparable<CalendarContext>
|
||||||
{
|
{
|
||||||
public int? Year { get; } = year;
|
public int? Year { get; } = year;
|
||||||
public int? Month { get; } = month;
|
public int? Month { get; } = month;
|
||||||
|
|||||||
@@ -95,13 +95,13 @@ public class CalendarView : TemplatedControl
|
|||||||
|
|
||||||
private CalendarContext _contextDate = new();
|
private CalendarContext _contextDate = new();
|
||||||
|
|
||||||
public static readonly DirectProperty<CalendarView, CalendarContext> ContextDateProperty = AvaloniaProperty.RegisterDirect<CalendarView, CalendarContext>(
|
internal static readonly DirectProperty<CalendarView, CalendarContext> ContextDateProperty = AvaloniaProperty.RegisterDirect<CalendarView, CalendarContext>(
|
||||||
nameof(ContextDate), o => o.ContextDate, (o, v) => o.ContextDate = v);
|
nameof(ContextDate), o => o.ContextDate, (o, v) => o.ContextDate = v);
|
||||||
|
|
||||||
public CalendarContext ContextDate
|
internal CalendarContext ContextDate
|
||||||
{
|
{
|
||||||
get => _contextDate;
|
get => _contextDate;
|
||||||
internal set => SetAndRaise(ContextDateProperty, ref _contextDate, value);
|
set => SetAndRaise(ContextDateProperty, ref _contextDate, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsTodayHighlighted
|
public bool IsTodayHighlighted
|
||||||
@@ -535,7 +535,7 @@ public class CalendarView : TemplatedControl
|
|||||||
IsEnabledProperty.SetValue(canNext, _nextButton, _fastNextButton);
|
IsEnabledProperty.SetValue(canNext, _nextButton, _fastNextButton);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MarkDates(DateTime? startDate = null, DateTime? endDate = null, DateTime? previewStartDate = null, DateTime? previewEndDate = null)
|
internal void MarkDates(DateTime? startDate = null, DateTime? endDate = null, DateTime? previewStartDate = null, DateTime? previewEndDate = null)
|
||||||
{
|
{
|
||||||
_start = startDate;
|
_start = startDate;
|
||||||
_end = endDate;
|
_end = endDate;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public class CalendarYearButton : ContentControl
|
|||||||
|
|
||||||
internal CalendarViewMode Mode { get; private set; }
|
internal CalendarViewMode Mode { get; private set; }
|
||||||
|
|
||||||
public event EventHandler<CalendarDayButtonEventArgs> ItemSelected
|
public event EventHandler<CalendarYearButtonEventArgs> ItemSelected
|
||||||
{
|
{
|
||||||
add => AddHandler(ItemSelectedEvent, value);
|
add => AddHandler(ItemSelectedEvent, value);
|
||||||
remove => RemoveHandler(ItemSelectedEvent, value);
|
remove => RemoveHandler(ItemSelectedEvent, value);
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ using Avalonia.Controls.Primitives;
|
|||||||
using Avalonia.Data;
|
using Avalonia.Data;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.VisualTree;
|
|
||||||
using Irihi.Avalonia.Shared.Common;
|
|
||||||
using Irihi.Avalonia.Shared.Contracts;
|
using Irihi.Avalonia.Shared.Contracts;
|
||||||
using Irihi.Avalonia.Shared.Helpers;
|
using Irihi.Avalonia.Shared.Helpers;
|
||||||
|
|
||||||
@@ -49,6 +47,7 @@ public class DatePicker: DatePickerBase, IClearControl
|
|||||||
|
|
||||||
static DatePicker()
|
static DatePicker()
|
||||||
{
|
{
|
||||||
|
FocusableProperty.OverrideDefaultValue<DatePicker>(true);
|
||||||
SelectedDateProperty.Changed.AddClassHandler<DatePicker, DateTime?>((picker, args) =>
|
SelectedDateProperty.Changed.AddClassHandler<DatePicker, DateTime?>((picker, args) =>
|
||||||
picker.OnSelectionChanged(args));
|
picker.OnSelectionChanged(args));
|
||||||
}
|
}
|
||||||
@@ -64,7 +63,6 @@ public class DatePicker: DatePickerBase, IClearControl
|
|||||||
|
|
||||||
GotFocusEvent.RemoveHandler(OnTextBoxGetFocus, _textBox);
|
GotFocusEvent.RemoveHandler(OnTextBoxGetFocus, _textBox);
|
||||||
TextBox.TextChangedEvent.RemoveHandler(OnTextChanged, _textBox);
|
TextBox.TextChangedEvent.RemoveHandler(OnTextChanged, _textBox);
|
||||||
PointerPressedEvent.RemoveHandler(OnTextBoxPointerPressed, _textBox);
|
|
||||||
Button.ClickEvent.RemoveHandler(OnButtonClick, _button);
|
Button.ClickEvent.RemoveHandler(OnButtonClick, _button);
|
||||||
CalendarView.DateSelectedEvent.RemoveHandler(OnDateSelected, _calendar);
|
CalendarView.DateSelectedEvent.RemoveHandler(OnDateSelected, _calendar);
|
||||||
|
|
||||||
@@ -73,10 +71,9 @@ public class DatePicker: DatePickerBase, IClearControl
|
|||||||
_textBox = e.NameScope.Find<TextBox>(PART_TextBox);
|
_textBox = e.NameScope.Find<TextBox>(PART_TextBox);
|
||||||
_calendar = e.NameScope.Find<CalendarView>(PART_Calendar);
|
_calendar = e.NameScope.Find<CalendarView>(PART_Calendar);
|
||||||
|
|
||||||
Button.ClickEvent.AddHandler(OnButtonClick, RoutingStrategies.Bubble, true, _button);
|
Button.ClickEvent.AddHandler(OnButtonClick, RoutingStrategies.Bubble, false, _button);
|
||||||
GotFocusEvent.AddHandler(OnTextBoxGetFocus, _textBox);
|
GotFocusEvent.AddHandler(OnTextBoxGetFocus, _textBox);
|
||||||
TextBox.TextChangedEvent.AddHandler(OnTextChanged, _textBox);
|
TextBox.TextChangedEvent.AddHandler(OnTextChanged, _textBox);
|
||||||
PointerPressedEvent.AddHandler(OnTextBoxPointerPressed, RoutingStrategies.Tunnel, false, _textBox);
|
|
||||||
CalendarView.DateSelectedEvent.AddHandler(OnDateSelected, RoutingStrategies.Bubble, true, _calendar);
|
CalendarView.DateSelectedEvent.AddHandler(OnDateSelected, RoutingStrategies.Bubble, true, _calendar);
|
||||||
SyncSelectedDateToText(SelectedDate);
|
SyncSelectedDateToText(SelectedDate);
|
||||||
}
|
}
|
||||||
@@ -89,19 +86,10 @@ public class DatePicker: DatePickerBase, IClearControl
|
|||||||
|
|
||||||
private void OnButtonClick(object? sender, RoutedEventArgs e)
|
private void OnButtonClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Focus(NavigationMethod.Pointer);
|
if(IsFocused)
|
||||||
|
{
|
||||||
SetCurrentValue(IsDropdownOpenProperty, !IsDropdownOpen);
|
SetCurrentValue(IsDropdownOpenProperty, !IsDropdownOpen);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTextBoxPointerPressed(object? sender, PointerPressedEventArgs e)
|
|
||||||
{
|
|
||||||
if (_calendar is not null)
|
|
||||||
{
|
|
||||||
var date = SelectedDate ?? DateTime.Today;
|
|
||||||
_calendar.ContextDate = new CalendarContext(date.Year, date.Month);
|
|
||||||
_calendar.UpdateDayButtons();
|
|
||||||
}
|
|
||||||
SetCurrentValue(IsDropdownOpenProperty, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -186,6 +174,7 @@ public class DatePicker: DatePickerBase, IClearControl
|
|||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnKeyDown(KeyEventArgs e)
|
protected override void OnKeyDown(KeyEventArgs e)
|
||||||
@@ -213,4 +202,40 @@ public class DatePicker: DatePickerBase, IClearControl
|
|||||||
{
|
{
|
||||||
SetCurrentValue(SelectedDateProperty, null);
|
SetCurrentValue(SelectedDateProperty, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnGotFocus(GotFocusEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnGotFocus(e);
|
||||||
|
FocusChanged(IsKeyboardFocusWithin);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnLostFocus(RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnLostFocus(e);
|
||||||
|
FocusChanged(IsKeyboardFocusWithin);
|
||||||
|
var top = TopLevel.GetTopLevel(this);
|
||||||
|
var element = top?.FocusManager?.GetFocusedElement();
|
||||||
|
if (element is Visual v && _popup?.IsInsidePopup(v)==true)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SetCurrentValue(IsDropdownOpenProperty, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isFocused;
|
||||||
|
private void FocusChanged(bool hasFocus)
|
||||||
|
{
|
||||||
|
bool wasFocused = _isFocused;
|
||||||
|
_isFocused = hasFocus;
|
||||||
|
|
||||||
|
if (hasFocus)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!wasFocused && _textBox != null)
|
||||||
|
{
|
||||||
|
_textBox.Focus();
|
||||||
|
_textBox.SelectAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -46,7 +46,7 @@ internal static class DateTimeHelper
|
|||||||
public static (int start, int end) GetDecadeViewRangeByYear(int year)
|
public static (int start, int end) GetDecadeViewRangeByYear(int year)
|
||||||
{
|
{
|
||||||
int start = year / 10 * 10;
|
int start = year / 10 * 10;
|
||||||
return new ValueTuple<int, int>(start, start + 10);
|
return new ValueTuple<int, int>(start, start + 10 - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static (int start, int end) GetCenturyViewRangeByYear(int year)
|
public static (int start, int end) GetCenturyViewRangeByYear(int year)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Ursa.Controls;
|
|||||||
[TemplatePart(PART_Popup, typeof(Popup))]
|
[TemplatePart(PART_Popup, typeof(Popup))]
|
||||||
[TemplatePart(PART_TextBox, typeof(TextBox))]
|
[TemplatePart(PART_TextBox, typeof(TextBox))]
|
||||||
[TemplatePart(PART_Calendar, typeof(CalendarView))]
|
[TemplatePart(PART_Calendar, typeof(CalendarView))]
|
||||||
[TemplatePart(PART_TimePicker, typeof(TimePicker))]
|
[TemplatePart(PART_TimePicker, typeof(TimePickerPresenter))]
|
||||||
public class DateTimePicker : DatePickerBase
|
public class DateTimePicker : DatePickerBase
|
||||||
{
|
{
|
||||||
public const string PART_Button = "PART_Button";
|
public const string PART_Button = "PART_Button";
|
||||||
@@ -76,6 +76,7 @@ public class DateTimePicker : DatePickerBase
|
|||||||
|
|
||||||
private void OnSelectionChanged(AvaloniaPropertyChangedEventArgs<DateTime?> args)
|
private void OnSelectionChanged(AvaloniaPropertyChangedEventArgs<DateTime?> args)
|
||||||
{
|
{
|
||||||
|
if (_fromText) return;
|
||||||
SyncSelectedDateToText(args.NewValue.Value);
|
SyncSelectedDateToText(args.NewValue.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,14 +86,14 @@ public class DateTimePicker : DatePickerBase
|
|||||||
{
|
{
|
||||||
_textBox?.SetValue(TextBox.TextProperty, null);
|
_textBox?.SetValue(TextBox.TextProperty, null);
|
||||||
_calendar?.ClearSelection();
|
_calendar?.ClearSelection();
|
||||||
_timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, null);
|
_timePickerPresenter?.SyncTime(null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_textBox?.SetValue(TextBox.TextProperty,
|
_textBox?.SetValue(TextBox.TextProperty,
|
||||||
date.Value.ToString(DisplayFormat ?? CultureInfo.InvariantCulture.DateTimeFormat.FullDateTimePattern));
|
date.Value.ToString(DisplayFormat ?? CultureInfo.InvariantCulture.DateTimeFormat.FullDateTimePattern));
|
||||||
_calendar?.MarkDates(date.Value.Date, date.Value.Date);
|
_calendar?.MarkDates(date.Value.Date, date.Value.Date);
|
||||||
_timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, date.Value.TimeOfDay);
|
_timePickerPresenter?.SyncTime(date.Value.TimeOfDay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,12 +176,14 @@ public class DateTimePicker : DatePickerBase
|
|||||||
var date = SelectedDate ?? DateTime.Now;
|
var date = SelectedDate ?? DateTime.Now;
|
||||||
_calendar.ContextDate = new CalendarContext(date.Year, date.Month);
|
_calendar.ContextDate = new CalendarContext(date.Year, date.Month);
|
||||||
_calendar.UpdateDayButtons();
|
_calendar.UpdateDayButtons();
|
||||||
_timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, SelectedDate?.TimeOfDay);
|
_timePickerPresenter?.SyncTime(SelectedDate?.TimeOfDay);
|
||||||
}
|
}
|
||||||
|
|
||||||
SetCurrentValue(IsDropdownOpenProperty, true);
|
SetCurrentValue(IsDropdownOpenProperty, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool _fromText = false;
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void OnTextChanged(object? sender, TextChangedEventArgs e)
|
private void OnTextChanged(object? sender, TextChangedEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -189,11 +192,13 @@ public class DateTimePicker : DatePickerBase
|
|||||||
|
|
||||||
private void SetSelectedDate(bool fromText = false)
|
private void SetSelectedDate(bool fromText = false)
|
||||||
{
|
{
|
||||||
|
var temp = _fromText;
|
||||||
|
_fromText = fromText;
|
||||||
if (string.IsNullOrEmpty(_textBox?.Text))
|
if (string.IsNullOrEmpty(_textBox?.Text))
|
||||||
{
|
{
|
||||||
SetCurrentValue(SelectedDateProperty, null);
|
SetCurrentValue(SelectedDateProperty, null);
|
||||||
_calendar?.ClearSelection();
|
_calendar?.ClearSelection();
|
||||||
_timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, null);
|
_timePickerPresenter?.SyncTime(null);
|
||||||
}
|
}
|
||||||
else if (DisplayFormat is null || DisplayFormat.Length == 0)
|
else if (DisplayFormat is null || DisplayFormat.Length == 0)
|
||||||
{
|
{
|
||||||
@@ -201,7 +206,7 @@ public class DateTimePicker : DatePickerBase
|
|||||||
{
|
{
|
||||||
SetCurrentValue(SelectedDateProperty, defaultTime);
|
SetCurrentValue(SelectedDateProperty, defaultTime);
|
||||||
_calendar?.MarkDates(defaultTime.Date, defaultTime.Date);
|
_calendar?.MarkDates(defaultTime.Date, defaultTime.Date);
|
||||||
_timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, defaultTime.TimeOfDay);
|
_timePickerPresenter?.SyncTime(defaultTime.TimeOfDay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -217,16 +222,17 @@ public class DateTimePicker : DatePickerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
_calendar?.MarkDates(date.Date, date.Date);
|
_calendar?.MarkDates(date.Date, date.Date);
|
||||||
_timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, date.TimeOfDay);
|
_timePickerPresenter?.SyncTime(date.TimeOfDay);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SetCurrentValue(SelectedDateProperty, null);
|
SetCurrentValue(SelectedDateProperty, null);
|
||||||
if (!fromText) _textBox?.SetValue(TextBox.TextProperty, null);
|
if (!fromText) _textBox?.SetValue(TextBox.TextProperty, null);
|
||||||
_calendar?.ClearSelection();
|
_calendar?.ClearSelection();
|
||||||
_timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, null);
|
_timePickerPresenter?.SyncTime(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_fromText = temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTextBoxGetFocus(object? sender, GotFocusEventArgs e)
|
private void OnTextBoxGetFocus(object? sender, GotFocusEventArgs e)
|
||||||
@@ -236,7 +242,7 @@ public class DateTimePicker : DatePickerBase
|
|||||||
var date = SelectedDate ?? DateTime.Today;
|
var date = SelectedDate ?? DateTime.Today;
|
||||||
_calendar.ContextDate = _calendar.ContextDate.With(date.Year, date.Month);
|
_calendar.ContextDate = _calendar.ContextDate.With(date.Year, date.Month);
|
||||||
_calendar.UpdateDayButtons();
|
_calendar.UpdateDayButtons();
|
||||||
_timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, date.TimeOfDay);
|
_timePickerPresenter?.SyncTime(date.TimeOfDay);
|
||||||
}
|
}
|
||||||
|
|
||||||
SetCurrentValue(IsDropdownOpenProperty, true);
|
SetCurrentValue(IsDropdownOpenProperty, true);
|
||||||
|
|||||||
@@ -29,24 +29,22 @@ public class TimePicker : TimePickerBase, IClearControl
|
|||||||
public static readonly StyledProperty<string?> WatermarkProperty = AvaloniaProperty.Register<TimePicker, string?>(
|
public static readonly StyledProperty<string?> WatermarkProperty = AvaloniaProperty.Register<TimePicker, string?>(
|
||||||
nameof(Watermark));
|
nameof(Watermark));
|
||||||
|
|
||||||
private bool _suppressTextPresenterEvent;
|
|
||||||
|
|
||||||
private Button? _button;
|
private Button? _button;
|
||||||
private TimePickerPresenter? _presenter;
|
|
||||||
private TextBox? _textBox;
|
|
||||||
|
|
||||||
|
private bool _isFocused;
|
||||||
|
private Popup? _popup;
|
||||||
|
private TimePickerPresenter? _presenter;
|
||||||
|
|
||||||
|
private bool _suppressTextPresenterEvent;
|
||||||
|
private TextBox? _textBox;
|
||||||
|
|
||||||
static TimePicker()
|
static TimePicker()
|
||||||
{
|
{
|
||||||
|
FocusableProperty.OverrideDefaultValue<TimePicker>(true);
|
||||||
SelectedTimeProperty.Changed.AddClassHandler<TimePicker, TimeSpan?>((picker, args) =>
|
SelectedTimeProperty.Changed.AddClassHandler<TimePicker, TimeSpan?>((picker, args) =>
|
||||||
picker.OnSelectionChanged(args));
|
picker.OnSelectionChanged(args));
|
||||||
DisplayFormatProperty.Changed.AddClassHandler<TimePicker, string?>((picker, args) => picker.OnDisplayFormatChanged(args));
|
DisplayFormatProperty.Changed.AddClassHandler<TimePicker, string?>((picker, args) =>
|
||||||
}
|
picker.OnDisplayFormatChanged(args));
|
||||||
|
|
||||||
private void OnDisplayFormatChanged(AvaloniaPropertyChangedEventArgs<string?> _)
|
|
||||||
{
|
|
||||||
if (_textBox is null) return;
|
|
||||||
SyncTimeToText(SelectedTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string? Watermark
|
public string? Watermark
|
||||||
@@ -63,56 +61,64 @@ public class TimePicker : TimePickerBase, IClearControl
|
|||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
|
SetCurrentValue(SelectedTimeProperty, null);
|
||||||
Focus(NavigationMethod.Pointer);
|
Focus(NavigationMethod.Pointer);
|
||||||
_presenter?.SetValue(TimePickerPresenter.TimeProperty, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnDisplayFormatChanged(AvaloniaPropertyChangedEventArgs<string?> _)
|
||||||
|
{
|
||||||
|
if (_textBox is null) return;
|
||||||
|
SyncTimeToText(SelectedTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnPointerPressed(e);
|
||||||
|
if (!e.Handled && e.Source is Visual source)
|
||||||
|
if (_popup?.IsInsidePopup(source) == true)
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnApplyTemplate(e);
|
base.OnApplyTemplate(e);
|
||||||
|
|
||||||
GotFocusEvent.RemoveHandler(OnTextBoxGetFocus, _textBox);
|
GotFocusEvent.RemoveHandler(OnTextBoxGetFocus, _textBox);
|
||||||
TextBox.TextChangedEvent.RemoveHandler(OnTextChanged, _textBox);
|
TextBox.TextChangedEvent.RemoveHandler(OnTextChanged, _textBox);
|
||||||
PointerPressedEvent.RemoveHandler(OnTextBoxPointerPressed, _textBox);
|
|
||||||
Button.ClickEvent.RemoveHandler(OnButtonClick, _button);
|
Button.ClickEvent.RemoveHandler(OnButtonClick, _button);
|
||||||
TimePickerPresenter.SelectedTimeChangedEvent.RemoveHandler(OnPresenterTimeChanged, _presenter);
|
TimePickerPresenter.SelectedTimeChangedEvent.RemoveHandler(OnPresenterTimeChanged, _presenter);
|
||||||
|
|
||||||
_textBox = e.NameScope.Find<TextBox>(PART_TextBox);
|
_textBox = e.NameScope.Find<TextBox>(PART_TextBox);
|
||||||
e.NameScope.Find<Popup>(PartNames.PART_Popup);
|
_popup = e.NameScope.Find<Popup>(PartNames.PART_Popup);
|
||||||
_presenter = e.NameScope.Find<TimePickerPresenter>(PART_Presenter);
|
_presenter = e.NameScope.Find<TimePickerPresenter>(PART_Presenter);
|
||||||
_button = e.NameScope.Find<Button>(PART_Button);
|
_button = e.NameScope.Find<Button>(PART_Button);
|
||||||
|
|
||||||
GotFocusEvent.AddHandler(OnTextBoxGetFocus, _textBox);
|
GotFocusEvent.AddHandler(OnTextBoxGetFocus, _textBox);
|
||||||
TextBox.TextChangedEvent.AddHandler(OnTextChanged, _textBox);
|
TextBox.TextChangedEvent.AddHandler(OnTextChanged, _textBox);
|
||||||
PointerPressedEvent.AddHandler(OnTextBoxPointerPressed, RoutingStrategies.Tunnel, false, _textBox);
|
|
||||||
Button.ClickEvent.AddHandler(OnButtonClick, _button);
|
Button.ClickEvent.AddHandler(OnButtonClick, _button);
|
||||||
TimePickerPresenter.SelectedTimeChangedEvent.AddHandler(OnPresenterTimeChanged, _presenter);
|
TimePickerPresenter.SelectedTimeChangedEvent.AddHandler(OnPresenterTimeChanged, _presenter);
|
||||||
|
|
||||||
// SetCurrentValue(SelectedTimeProperty, DateTime.Now.TimeOfDay);
|
_presenter?.SyncTime(SelectedTime);
|
||||||
_presenter?.SetValue(TimePickerPresenter.TimeProperty, SelectedTime);
|
|
||||||
SyncTimeToText(SelectedTime);
|
SyncTimeToText(SelectedTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPresenterTimeChanged(object? sender, TimeChangedEventArgs e)
|
private void OnPresenterTimeChanged(object? sender, TimeChangedEventArgs e)
|
||||||
{
|
{
|
||||||
|
if (!IsInitialized) return;
|
||||||
if (_suppressTextPresenterEvent) return;
|
if (_suppressTextPresenterEvent) return;
|
||||||
SetCurrentValue(SelectedTimeProperty, e.NewTime);
|
SetCurrentValue(SelectedTimeProperty, e.NewTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnButtonClick(object? sender, RoutedEventArgs e)
|
private void OnButtonClick(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
Focus(NavigationMethod.Pointer);
|
if (IsFocused) SetCurrentValue(IsDropdownOpenProperty, !IsDropdownOpen);
|
||||||
SetCurrentValue(IsDropdownOpenProperty, !IsDropdownOpen);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnTextBoxPointerPressed(object? sender, PointerPressedEventArgs e)
|
|
||||||
{
|
|
||||||
SetCurrentValue(IsDropdownOpenProperty, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTextBoxGetFocus(object? sender, GotFocusEventArgs e)
|
private void OnTextBoxGetFocus(object? sender, GotFocusEventArgs e)
|
||||||
{
|
{
|
||||||
// SetCurrentValue(IsDropdownOpenProperty, true);
|
SetCurrentValue(IsDropdownOpenProperty, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnKeyDown(KeyEventArgs e)
|
protected override void OnKeyDown(KeyEventArgs e)
|
||||||
@@ -123,20 +129,17 @@ public class TimePicker : TimePickerBase, IClearControl
|
|||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.Key == Key.Down)
|
if (e.Key == Key.Down)
|
||||||
{
|
{
|
||||||
SetCurrentValue(IsDropdownOpenProperty, true);
|
SetCurrentValue(IsDropdownOpenProperty, true);
|
||||||
e.Handled = true;
|
e.Handled = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.Key == Key.Tab)
|
if (e.Key == Key.Tab)
|
||||||
{
|
{
|
||||||
SetCurrentValue(IsDropdownOpenProperty, false);
|
SetCurrentValue(IsDropdownOpenProperty, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
base.OnKeyDown(e);
|
base.OnKeyDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,17 +148,17 @@ public class TimePicker : TimePickerBase, IClearControl
|
|||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(_textBox?.Text))
|
if (string.IsNullOrEmpty(_textBox?.Text))
|
||||||
{
|
{
|
||||||
TimePickerPresenter.TimeProperty.SetValue(null, _presenter);
|
_presenter?.SyncTime(null);
|
||||||
}
|
}
|
||||||
else if (DisplayFormat is null || DisplayFormat.Length == 0)
|
else if (DisplayFormat is null || DisplayFormat.Length == 0)
|
||||||
{
|
{
|
||||||
if (TimeSpan.TryParse(_textBox?.Text, out var defaultTime))
|
if (TimeSpan.TryParse(_textBox?.Text, out var defaultTime)) _presenter?.SyncTime(defaultTime);
|
||||||
TimePickerPresenter.TimeProperty.SetValue(defaultTime, _presenter);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (DateTime.TryParseExact(_textBox?.Text, DisplayFormat, CultureInfo.CurrentUICulture, DateTimeStyles.None,
|
if (DateTime.TryParseExact(_textBox?.Text, DisplayFormat, CultureInfo.CurrentUICulture, DateTimeStyles.None,
|
||||||
out var time)) TimePickerPresenter.TimeProperty.SetValue(time.TimeOfDay, _presenter);
|
out var time))
|
||||||
|
_presenter?.SyncTime(time.TimeOfDay);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +166,7 @@ public class TimePicker : TimePickerBase, IClearControl
|
|||||||
{
|
{
|
||||||
if (_textBox is null) return;
|
if (_textBox is null) return;
|
||||||
_suppressTextPresenterEvent = true;
|
_suppressTextPresenterEvent = true;
|
||||||
_presenter?.SetValue(TimePickerPresenter.TimeProperty, args.NewValue.Value);
|
_presenter?.SyncTime(args.NewValue.Value);
|
||||||
SyncTimeToText(args.NewValue.Value);
|
SyncTimeToText(args.NewValue.Value);
|
||||||
_suppressTextPresenterEvent = false;
|
_suppressTextPresenterEvent = false;
|
||||||
}
|
}
|
||||||
@@ -176,6 +179,7 @@ public class TimePicker : TimePickerBase, IClearControl
|
|||||||
_textBox.Text = null;
|
_textBox.Text = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var date = new DateTime(1, 1, 1, time.Value.Hours, time.Value.Minutes, time.Value.Seconds);
|
var date = new DateTime(1, 1, 1, time.Value.Hours, time.Value.Minutes, time.Value.Seconds);
|
||||||
var text = date.ToString(DisplayFormat);
|
var text = date.ToString(DisplayFormat);
|
||||||
_textBox.Text = text;
|
_textBox.Text = text;
|
||||||
@@ -199,4 +203,31 @@ public class TimePicker : TimePickerBase, IClearControl
|
|||||||
base.UpdateDataValidation(property, state, error);
|
base.UpdateDataValidation(property, state, error);
|
||||||
if (property == SelectedTimeProperty) DataValidationErrors.SetError(this, error);
|
if (property == SelectedTimeProperty) DataValidationErrors.SetError(this, error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnGotFocus(GotFocusEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnGotFocus(e);
|
||||||
|
// SetCurrentValue(IsDropdownOpenProperty, true);
|
||||||
|
FocusChanged(IsKeyboardFocusWithin);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnLostFocus(RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnLostFocus(e);
|
||||||
|
FocusChanged(IsKeyboardFocusWithin);
|
||||||
|
var top = TopLevel.GetTopLevel(this);
|
||||||
|
var element = top?.FocusManager?.GetFocusedElement();
|
||||||
|
if (element is Visual v && _popup?.IsInsidePopup(v) == true) return;
|
||||||
|
if (element == _textBox) return;
|
||||||
|
SetCurrentValue(IsDropdownOpenProperty, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FocusChanged(bool hasFocus)
|
||||||
|
{
|
||||||
|
var wasFocused = _isFocused;
|
||||||
|
_isFocused = hasFocus;
|
||||||
|
if (hasFocus)
|
||||||
|
if (!wasFocused && _textBox != null)
|
||||||
|
_textBox.Focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Metadata;
|
using Avalonia.Controls.Metadata;
|
||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Input;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Irihi.Avalonia.Shared.Helpers;
|
using Irihi.Avalonia.Shared.Helpers;
|
||||||
|
|
||||||
@@ -43,20 +44,20 @@ public class TimePickerPresenter : TemplatedControl
|
|||||||
|
|
||||||
public static readonly StyledProperty<int> MinuteIncrementProperty =
|
public static readonly StyledProperty<int> MinuteIncrementProperty =
|
||||||
AvaloniaProperty.Register<TimePickerPresenter, int>(
|
AvaloniaProperty.Register<TimePickerPresenter, int>(
|
||||||
nameof(MinuteIncrement));
|
nameof(MinuteIncrement), 1);
|
||||||
|
|
||||||
public static readonly StyledProperty<int> SecondIncrementProperty =
|
public static readonly StyledProperty<int> SecondIncrementProperty =
|
||||||
AvaloniaProperty.Register<TimePickerPresenter, int>(
|
AvaloniaProperty.Register<TimePickerPresenter, int>(
|
||||||
nameof(SecondIncrement));
|
nameof(SecondIncrement), 1);
|
||||||
|
|
||||||
public static readonly StyledProperty<TimeSpan?> TimeProperty =
|
|
||||||
AvaloniaProperty.Register<TimePickerPresenter, TimeSpan?>(
|
|
||||||
nameof(Time));
|
|
||||||
|
|
||||||
public static readonly StyledProperty<string> PanelFormatProperty =
|
public static readonly StyledProperty<string> PanelFormatProperty =
|
||||||
AvaloniaProperty.Register<TimePickerPresenter, string>(
|
AvaloniaProperty.Register<TimePickerPresenter, string>(
|
||||||
nameof(PanelFormat), "HH mm ss t");
|
nameof(PanelFormat), "HH mm ss t");
|
||||||
|
|
||||||
|
public static readonly RoutedEvent<TimeChangedEventArgs> SelectedTimeChangedEvent =
|
||||||
|
RoutedEvent.Register<TimePickerPresenter, TimeChangedEventArgs>(
|
||||||
|
nameof(SelectedTimeChanged), RoutingStrategies.Bubble);
|
||||||
|
|
||||||
private Control? _ampmScrollPanel;
|
private Control? _ampmScrollPanel;
|
||||||
private DateTimePickerPanel? _ampmSelector;
|
private DateTimePickerPanel? _ampmSelector;
|
||||||
private Control? _firstSeparator;
|
private Control? _firstSeparator;
|
||||||
@@ -65,26 +66,21 @@ public class TimePickerPresenter : TemplatedControl
|
|||||||
private DateTimePickerPanel? _hourSelector;
|
private DateTimePickerPanel? _hourSelector;
|
||||||
private Control? _minuteScrollPanel;
|
private Control? _minuteScrollPanel;
|
||||||
private DateTimePickerPanel? _minuteSelector;
|
private DateTimePickerPanel? _minuteSelector;
|
||||||
|
|
||||||
private Grid? _pickerContainer;
|
private Grid? _pickerContainer;
|
||||||
private Control? _secondScrollPanel;
|
private Control? _secondScrollPanel;
|
||||||
private DateTimePickerPanel? _secondSelector;
|
private DateTimePickerPanel? _secondSelector;
|
||||||
private Control? _secondSeparator;
|
private Control? _secondSeparator;
|
||||||
|
|
||||||
|
private bool _surpressTimeEvent = true;
|
||||||
private Control? _thirdSeparator;
|
private Control? _thirdSeparator;
|
||||||
internal TimeSpan TimeHolder;
|
|
||||||
private bool _updateFromTimeChange;
|
|
||||||
private bool _use12Clock;
|
private bool _use12Clock;
|
||||||
|
internal TimeSpan? TimeHolder;
|
||||||
|
|
||||||
static TimePickerPresenter()
|
static TimePickerPresenter()
|
||||||
{
|
{
|
||||||
PanelFormatProperty.Changed.AddClassHandler<TimePickerPresenter, string>((presenter, args) =>
|
PanelFormatProperty.Changed.AddClassHandler<TimePickerPresenter, string>((presenter, args) =>
|
||||||
presenter.OnPanelFormatChanged(args));
|
presenter.OnPanelFormatChanged(args));
|
||||||
TimeProperty.Changed.AddClassHandler<TimePickerPresenter, TimeSpan?>((presenter, args) =>
|
|
||||||
presenter.OnTimeChanged(args));
|
|
||||||
}
|
|
||||||
|
|
||||||
public TimePickerPresenter()
|
|
||||||
{
|
|
||||||
// SetCurrentValue(TimeProperty, DateTime.Now.TimeOfDay);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool NeedsConfirmation
|
public bool NeedsConfirmation
|
||||||
@@ -105,40 +101,18 @@ public class TimePickerPresenter : TemplatedControl
|
|||||||
set => SetValue(SecondIncrementProperty, value);
|
set => SetValue(SecondIncrementProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeSpan? Time
|
|
||||||
{
|
|
||||||
get => GetValue(TimeProperty);
|
|
||||||
set => SetValue(TimeProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public string PanelFormat
|
public string PanelFormat
|
||||||
{
|
{
|
||||||
get => GetValue(PanelFormatProperty);
|
get => GetValue(PanelFormatProperty);
|
||||||
set => SetValue(PanelFormatProperty, value);
|
set => SetValue(PanelFormatProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly RoutedEvent<TimeChangedEventArgs> SelectedTimeChangedEvent =
|
|
||||||
RoutedEvent.Register<TimePickerPresenter, TimeChangedEventArgs>(
|
|
||||||
nameof(SelectedTimeChanged), RoutingStrategies.Bubble);
|
|
||||||
|
|
||||||
public event EventHandler<TimeChangedEventArgs> SelectedTimeChanged
|
public event EventHandler<TimeChangedEventArgs> SelectedTimeChanged
|
||||||
{
|
{
|
||||||
add => AddHandler(SelectedTimeChangedEvent, value);
|
add => AddHandler(SelectedTimeChangedEvent, value);
|
||||||
remove => RemoveHandler(SelectedTimeChangedEvent, value);
|
remove => RemoveHandler(SelectedTimeChangedEvent, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTimeChanged(AvaloniaPropertyChangedEventArgs<TimeSpan?> args)
|
|
||||||
{
|
|
||||||
_updateFromTimeChange = true;
|
|
||||||
UpdatePanelsFromSelectedTime(args.NewValue.Value);
|
|
||||||
_updateFromTimeChange = false;
|
|
||||||
if (args.OldValue.Value != args.NewValue.Value)
|
|
||||||
{
|
|
||||||
RaiseEvent(new TimeChangedEventArgs(args.OldValue.Value, args.NewValue.Value)
|
|
||||||
{ RoutedEvent = SelectedTimeChangedEvent, Source = this });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnPanelFormatChanged(AvaloniaPropertyChangedEventArgs<string> args)
|
private void OnPanelFormatChanged(AvaloniaPropertyChangedEventArgs<string> args)
|
||||||
{
|
{
|
||||||
var format = args.NewValue.Value;
|
var format = args.NewValue.Value;
|
||||||
@@ -180,6 +154,7 @@ public class TimePickerPresenter : TemplatedControl
|
|||||||
{
|
{
|
||||||
panels.Add(_ampmScrollPanel);
|
panels.Add(_ampmScrollPanel);
|
||||||
_ampmSelector?.SetValue(DateTimePickerPanel.ItemFormatProperty, part);
|
_ampmSelector?.SetValue(DateTimePickerPanel.ItemFormatProperty, part);
|
||||||
|
if (_ampmSelector is not null) _ampmSelector.IsEnabled = _use12Clock;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -191,6 +166,7 @@ public class TimePickerPresenter : TemplatedControl
|
|||||||
if (panels.Count < 1) return;
|
if (panels.Count < 1) return;
|
||||||
IsVisibleProperty.SetValue(false, _hourScrollPanel, _minuteScrollPanel, _secondScrollPanel, _ampmScrollPanel,
|
IsVisibleProperty.SetValue(false, _hourScrollPanel, _minuteScrollPanel, _secondScrollPanel, _ampmScrollPanel,
|
||||||
_firstSeparator, _secondSeparator, _thirdSeparator);
|
_firstSeparator, _secondSeparator, _thirdSeparator);
|
||||||
|
Grid.ColumnProperty.SetValue(0, _hourScrollPanel, _minuteScrollPanel, _secondScrollPanel, _ampmScrollPanel);
|
||||||
for (var i = 0; i < panels.Count; i++)
|
for (var i = 0; i < panels.Count; i++)
|
||||||
{
|
{
|
||||||
var panel = panels[i];
|
var panel = panels[i];
|
||||||
@@ -211,47 +187,63 @@ public class TimePickerPresenter : TemplatedControl
|
|||||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnApplyTemplate(e);
|
base.OnApplyTemplate(e);
|
||||||
if (_hourSelector is not null) _hourSelector.SelectionChanged -= OnPanelSelectionChanged;
|
|
||||||
if (_minuteSelector is not null) _minuteSelector.SelectionChanged -= OnPanelSelectionChanged;
|
|
||||||
if (_secondSelector is not null) _secondSelector.SelectionChanged -= OnPanelSelectionChanged;
|
|
||||||
if (_ampmSelector is not null) _ampmSelector.SelectionChanged -= OnPanelSelectionChanged;
|
|
||||||
_hourSelector = e.NameScope.Find<DateTimePickerPanel>(PART_HourSelector);
|
_hourSelector = e.NameScope.Find<DateTimePickerPanel>(PART_HourSelector);
|
||||||
_minuteSelector = e.NameScope.Find<DateTimePickerPanel>(PART_MinuteSelector);
|
_minuteSelector = e.NameScope.Find<DateTimePickerPanel>(PART_MinuteSelector);
|
||||||
_secondSelector = e.NameScope.Find<DateTimePickerPanel>(PART_SecondSelector);
|
_secondSelector = e.NameScope.Find<DateTimePickerPanel>(PART_SecondSelector);
|
||||||
_ampmSelector = e.NameScope.Find<DateTimePickerPanel>(PART_AmPmSelector);
|
_ampmSelector = e.NameScope.Find<DateTimePickerPanel>(PART_AmPmSelector);
|
||||||
if (_hourSelector is not null) _hourSelector.SelectionChanged += OnPanelSelectionChanged;
|
|
||||||
if (_minuteSelector is not null) _minuteSelector.SelectionChanged += OnPanelSelectionChanged;
|
|
||||||
if (_secondSelector is not null) _secondSelector.SelectionChanged += OnPanelSelectionChanged;
|
|
||||||
if (_ampmSelector is not null) _ampmSelector.SelectionChanged += OnPanelSelectionChanged;
|
|
||||||
_pickerContainer = e.NameScope.Find<Grid>(PART_PickerContainer);
|
_pickerContainer = e.NameScope.Find<Grid>(PART_PickerContainer);
|
||||||
_hourScrollPanel = e.NameScope.Find<Control>(PART_HourScrollPanel);
|
_hourScrollPanel = e.NameScope.Find<Control>(PART_HourScrollPanel);
|
||||||
_minuteScrollPanel = e.NameScope.Find<Control>(PART_MinuteScrollPanel);
|
_minuteScrollPanel = e.NameScope.Find<Control>(PART_MinuteScrollPanel);
|
||||||
_secondScrollPanel = e.NameScope.Find<Control>(PART_SecondScrollPanel);
|
_secondScrollPanel = e.NameScope.Find<Control>(PART_SecondScrollPanel);
|
||||||
_ampmScrollPanel = e.NameScope.Find<Control>(PART_AmPmScrollPanel);
|
_ampmScrollPanel = e.NameScope.Find<Control>(PART_AmPmScrollPanel);
|
||||||
|
|
||||||
_firstSeparator = e.NameScope.Find<Control>(PART_FirstSeparator);
|
_firstSeparator = e.NameScope.Find<Control>(PART_FirstSeparator);
|
||||||
_secondSeparator = e.NameScope.Find<Control>(PART_SecondSeparator);
|
_secondSeparator = e.NameScope.Find<Control>(PART_SecondSeparator);
|
||||||
_thirdSeparator = e.NameScope.Find<Control>(PART_ThirdSeparator);
|
_thirdSeparator = e.NameScope.Find<Control>(PART_ThirdSeparator);
|
||||||
Initialize();
|
Initialize();
|
||||||
UpdatePanelLayout(PanelFormat);
|
UpdatePanelLayout(PanelFormat);
|
||||||
UpdatePanelsFromSelectedTime(Time);
|
_surpressTimeEvent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnLoaded(RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnLoaded(e);
|
||||||
|
UpdatePanelsFromSelectedTime(TimeHolder);
|
||||||
|
if (_hourSelector is not null) _hourSelector.SelectionChanged += OnPanelSelectionChanged;
|
||||||
|
if (_minuteSelector is not null) _minuteSelector.SelectionChanged += OnPanelSelectionChanged;
|
||||||
|
if (_secondSelector is not null) _secondSelector.SelectionChanged += OnPanelSelectionChanged;
|
||||||
|
if (_ampmSelector is not null) _ampmSelector.SelectionChanged += OnPanelSelectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnUnloaded(RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnUnloaded(e);
|
||||||
|
if (_hourSelector is not null) _hourSelector.SelectionChanged -= OnPanelSelectionChanged;
|
||||||
|
if (_minuteSelector is not null) _minuteSelector.SelectionChanged -= OnPanelSelectionChanged;
|
||||||
|
if (_secondSelector is not null) _secondSelector.SelectionChanged -= OnPanelSelectionChanged;
|
||||||
|
if (_ampmSelector is not null) _ampmSelector.SelectionChanged -= OnPanelSelectionChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPanelSelectionChanged(object? sender, System.EventArgs e)
|
private void OnPanelSelectionChanged(object? sender, System.EventArgs e)
|
||||||
{
|
{
|
||||||
if (_updateFromTimeChange) return;
|
if (_surpressTimeEvent) return;
|
||||||
if (!_use12Clock && Equals(sender, _ampmSelector)) return;
|
if (!_use12Clock && Equals(sender, _ampmSelector)) return;
|
||||||
var time = NeedsConfirmation ? TimeHolder : Time ?? DateTime.Now.TimeOfDay;
|
var time = TimeHolder ?? DateTime.Now.TimeOfDay;
|
||||||
var hour = _hourSelector?.SelectedValue ?? time.Hours;
|
var hour = _hourSelector?.SelectedValue ?? time.Hours;
|
||||||
var minute = _minuteSelector?.SelectedValue ?? time.Minutes;
|
var minute = _minuteSelector?.SelectedValue ?? time.Minutes;
|
||||||
var second = _secondSelector?.SelectedValue ?? time.Seconds;
|
var second = _secondSelector?.SelectedValue ?? time.Seconds;
|
||||||
var ampm = _ampmSelector?.SelectedValue ?? (time.Hours >= 12 ? 1 : 0);
|
var ampm = _ampmSelector?.SelectedValue ?? (time.Hours >= 12 ? 1 : 0);
|
||||||
if (_use12Clock)
|
if (_use12Clock)
|
||||||
|
{
|
||||||
hour = ampm switch
|
hour = ampm switch
|
||||||
{
|
{
|
||||||
0 when hour == 12 => 0,
|
0 when hour == 12 => 0,
|
||||||
1 when hour < 12 => hour + 12,
|
1 when hour < 12 => hour + 12,
|
||||||
_ => hour
|
_ => hour
|
||||||
};
|
};
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ampm = hour switch
|
ampm = hour switch
|
||||||
@@ -261,11 +253,23 @@ public class TimePickerPresenter : TemplatedControl
|
|||||||
};
|
};
|
||||||
SetIfChanged(_ampmSelector, ampm);
|
SetIfChanged(_ampmSelector, ampm);
|
||||||
}
|
}
|
||||||
|
|
||||||
var newTime = new TimeSpan(hour, minute, second);
|
var newTime = new TimeSpan(hour, minute, second);
|
||||||
if (NeedsConfirmation)
|
if (NeedsConfirmation)
|
||||||
|
{
|
||||||
TimeHolder = newTime;
|
TimeHolder = newTime;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
SetCurrentValue(TimeProperty, newTime);
|
{
|
||||||
|
if (_surpressTimeEvent) return;
|
||||||
|
RaiseEvent(new TimeChangedEventArgs(null, newTime) { RoutedEvent = SelectedTimeChangedEvent });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnPointerPressed(e);
|
||||||
|
OnPanelSelectionChanged(null, System.EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdatePanelsFromSelectedTime(TimeSpan? time)
|
private void UpdatePanelsFromSelectedTime(TimeSpan? time)
|
||||||
@@ -275,21 +279,19 @@ public class TimePickerPresenter : TemplatedControl
|
|||||||
{
|
{
|
||||||
var index = _use12Clock ? time.Value.Hours % 12 : time.Value.Hours;
|
var index = _use12Clock ? time.Value.Hours % 12 : time.Value.Hours;
|
||||||
if (_use12Clock && index == 0) index = 12;
|
if (_use12Clock && index == 0) index = 12;
|
||||||
SetIfChanged(_hourSelector, index);
|
SetIfChanged(_hourSelector, index, true);
|
||||||
}
|
}
|
||||||
SetIfChanged(_minuteSelector, time.Value.Minutes);
|
|
||||||
SetIfChanged(_secondSelector, time.Value.Seconds);
|
SetIfChanged(_minuteSelector, time.Value.Minutes, true);
|
||||||
|
SetIfChanged(_secondSelector, time.Value.Seconds, true);
|
||||||
var ampm = time.Value.Hours switch
|
var ampm = time.Value.Hours switch
|
||||||
{
|
{
|
||||||
>= 12 => 1,
|
>= 12 => 1,
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
|
|
||||||
SetIfChanged(_ampmSelector, ampm);
|
SetIfChanged(_ampmSelector, ampm, true);
|
||||||
if (_ampmSelector is not null)
|
if (_ampmSelector is not null) _ampmSelector.IsEnabled = _use12Clock;
|
||||||
{
|
|
||||||
_ampmSelector.IsEnabled = _use12Clock;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Initialize()
|
private void Initialize()
|
||||||
@@ -326,12 +328,23 @@ public class TimePickerPresenter : TemplatedControl
|
|||||||
|
|
||||||
public void Confirm()
|
public void Confirm()
|
||||||
{
|
{
|
||||||
if (NeedsConfirmation) SetCurrentValue(TimeProperty, TimeHolder);
|
if (NeedsConfirmation)
|
||||||
|
RaiseEvent(new TimeChangedEventArgs(null, TimeHolder) { RoutedEvent = SelectedTimeChangedEvent });
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetIfChanged(DateTimePickerPanel? panel, int index)
|
private void SetIfChanged(DateTimePickerPanel? panel, int index, bool surpress = false)
|
||||||
{
|
{
|
||||||
if (panel is null) return;
|
if (panel is null) return;
|
||||||
|
panel.SelectionChanged -= OnPanelSelectionChanged;
|
||||||
if (panel.SelectedValue != index) panel.SelectedValue = index;
|
if (panel.SelectedValue != index) panel.SelectedValue = index;
|
||||||
|
panel.SelectionChanged += OnPanelSelectionChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SyncTime(TimeSpan? time)
|
||||||
|
{
|
||||||
|
_surpressTimeEvent = true;
|
||||||
|
TimeHolder = time;
|
||||||
|
UpdatePanelsFromSelectedTime(time);
|
||||||
|
_surpressTimeEvent = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,15 +100,16 @@ public class TimeRangePicker : TimePickerBase, IClearControl
|
|||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
Focus(NavigationMethod.Pointer);
|
Focus(NavigationMethod.Pointer);
|
||||||
_startPresenter?.SetValue(TimePickerPresenter.TimeProperty, null);
|
_startPresenter?.SyncTime(null);
|
||||||
_endPresenter?.SetValue(TimePickerPresenter.TimeProperty, null);
|
_endPresenter?.SyncTime(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSelectionChanged(AvaloniaPropertyChangedEventArgs<TimeSpan?> args, bool start = true)
|
private void OnSelectionChanged(AvaloniaPropertyChangedEventArgs<TimeSpan?> args, bool start = true)
|
||||||
{
|
{
|
||||||
SyncTimeToText(args.NewValue.Value, start);
|
SyncTimeToText(args.NewValue.Value, start);
|
||||||
_suppressTextPresenterEvent = true;
|
_suppressTextPresenterEvent = true;
|
||||||
TimePickerPresenter.TimeProperty.SetValue(args.NewValue.Value, start ? _startPresenter : _endPresenter);
|
var presenter = start ? _startPresenter : _endPresenter;
|
||||||
|
presenter?.SyncTime(args.NewValue.Value);
|
||||||
_suppressTextPresenterEvent = false;
|
_suppressTextPresenterEvent = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,8 +151,8 @@ public class TimeRangePicker : TimePickerBase, IClearControl
|
|||||||
Button.ClickEvent.AddHandler(OnButtonClick, _button);
|
Button.ClickEvent.AddHandler(OnButtonClick, _button);
|
||||||
TimePickerPresenter.SelectedTimeChangedEvent.AddHandler(OnPresenterTimeChanged, _startPresenter, _endPresenter);
|
TimePickerPresenter.SelectedTimeChangedEvent.AddHandler(OnPresenterTimeChanged, _startPresenter, _endPresenter);
|
||||||
|
|
||||||
_startPresenter?.SetValue(TimePickerPresenter.TimeProperty, StartTime);
|
_startPresenter?.SyncTime(StartTime);
|
||||||
_endPresenter?.SetValue(TimePickerPresenter.TimeProperty, EndTime);
|
_endPresenter?.SyncTime(EndTime);
|
||||||
SyncTimeToText(StartTime);
|
SyncTimeToText(StartTime);
|
||||||
SyncTimeToText(EndTime, false);
|
SyncTimeToText(EndTime, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Headless;
|
||||||
|
using Avalonia.Headless.XUnit;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using Ursa.Controls;
|
||||||
|
|
||||||
|
namespace HeadlessTest.Ursa.Controls.DateTimePicker;
|
||||||
|
|
||||||
|
public class CalendarDayButtonTests
|
||||||
|
{
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnPointerReleased_RaisesDateSelectedEvent()
|
||||||
|
{
|
||||||
|
Window window = new Window();
|
||||||
|
var button = new CalendarDayButton();
|
||||||
|
var date = new DateTime(2023, 5, 15);
|
||||||
|
button.DataContext = date;
|
||||||
|
window.Content = button;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
int eventRaised = 0;
|
||||||
|
DateTime? eventContext = null;
|
||||||
|
|
||||||
|
void OnMouseClick(object? sender, CalendarDayButtonEventArgs args)
|
||||||
|
{
|
||||||
|
eventRaised++;
|
||||||
|
eventContext = args.Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.DateSelected += OnMouseClick;
|
||||||
|
window.MouseUp(new Point(10, 10), MouseButton.Left);
|
||||||
|
Assert.Equal(1, eventRaised);
|
||||||
|
Assert.Equal(date, eventContext);
|
||||||
|
button.DateSelected -= OnMouseClick;
|
||||||
|
eventContext = null;
|
||||||
|
window.MouseUp(new Point(10, 10), MouseButton.Left);
|
||||||
|
Assert.Null(eventContext);
|
||||||
|
Assert.Equal(1, eventRaised);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnPointerEntered_RaisesDatePreviewedEvent()
|
||||||
|
{
|
||||||
|
Window window = new Window();
|
||||||
|
var button = new CalendarDayButton();
|
||||||
|
var date = new DateTime(2023, 5, 15);
|
||||||
|
button.DataContext = date;
|
||||||
|
window.Content = button;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
int eventRaised = 0;
|
||||||
|
DateTime? eventContext = null;
|
||||||
|
|
||||||
|
void OnMouseEnter(object? sender, CalendarDayButtonEventArgs args)
|
||||||
|
{
|
||||||
|
eventRaised++;
|
||||||
|
eventContext = args.Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.DatePreviewed += OnMouseEnter;
|
||||||
|
window.MouseMove(new Point(10, 10));
|
||||||
|
Assert.Equal(1, eventRaised);
|
||||||
|
Assert.Equal(date, eventContext);
|
||||||
|
window.MouseMove(new Point(100, 100));
|
||||||
|
button.DatePreviewed -= OnMouseEnter;
|
||||||
|
eventContext = null;
|
||||||
|
window.MouseMove(new Point(10, 10));
|
||||||
|
Assert.Null(eventContext);
|
||||||
|
Assert.Equal(1, eventRaised);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Templates;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Ursa.Controls;
|
||||||
|
|
||||||
|
namespace HeadlessTest.Ursa.Controls.DateTimePicker;
|
||||||
|
|
||||||
|
internal static class CalendarViewHelper
|
||||||
|
{
|
||||||
|
internal static void ClickPrevious(this CalendarView calendarView)
|
||||||
|
{
|
||||||
|
var previousButton = calendarView.GetTemplateChildren()
|
||||||
|
.FirstOrDefault(a => a.Name == CalendarView.PART_PreviousButton);
|
||||||
|
Assert.IsType<Button>(previousButton);
|
||||||
|
var button = (Button)previousButton;
|
||||||
|
button.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ClickNext(this CalendarView calendarView)
|
||||||
|
{
|
||||||
|
var nextButton = calendarView.GetTemplateChildren()
|
||||||
|
.FirstOrDefault(a => a.Name == CalendarView.PART_NextButton);
|
||||||
|
Assert.IsType<Button>(nextButton);
|
||||||
|
var button = (Button)nextButton;
|
||||||
|
button.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ClickFastNext(this CalendarView calendarView)
|
||||||
|
{
|
||||||
|
var nextButton = calendarView.GetTemplateChildren()
|
||||||
|
.FirstOrDefault(a => a.Name == CalendarView.PART_FastNextButton);
|
||||||
|
Assert.IsType<Button>(nextButton);
|
||||||
|
var button = (Button)nextButton;
|
||||||
|
button.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ClickFastPrevious(this CalendarView calendarView)
|
||||||
|
{
|
||||||
|
var previousButton = calendarView.GetTemplateChildren()
|
||||||
|
.FirstOrDefault(a => a.Name == CalendarView.PART_FastPreviousButton);
|
||||||
|
Assert.IsType<Button>(previousButton);
|
||||||
|
var button = (Button)previousButton;
|
||||||
|
button.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ClickHeaderButton(this CalendarView calendarView)
|
||||||
|
{
|
||||||
|
var headerButton = calendarView.GetTemplateChildren()
|
||||||
|
.FirstOrDefault(a => a.Name == CalendarView.PART_HeaderButton);
|
||||||
|
Assert.IsType<Button>(headerButton);
|
||||||
|
var button = (Button)headerButton;
|
||||||
|
button.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,278 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Templates;
|
||||||
|
using Avalonia.Headless;
|
||||||
|
using Avalonia.Headless.XUnit;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
using Ursa.Controls;
|
||||||
|
|
||||||
|
namespace HeadlessTest.Ursa.Controls.DateTimePicker;
|
||||||
|
|
||||||
|
public class CalendarViewTests
|
||||||
|
{
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnNext_MonthMode_UpdatesToNextMonth()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var calendarView = new CalendarView
|
||||||
|
{ Mode = CalendarViewMode.Month };
|
||||||
|
window.Content = calendarView;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.SyncContextDate(new CalendarContext(2023, 5));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.ClickNext();
|
||||||
|
Assert.Equal(6, calendarView.ContextDate.Month);
|
||||||
|
Assert.Equal(2023, calendarView.ContextDate.Year);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnNext_MonthMode_FastForward_UpdatesToNextYear()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var calendarView = new CalendarView
|
||||||
|
{ Mode = CalendarViewMode.Month };
|
||||||
|
window.Content = calendarView;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.SyncContextDate(new CalendarContext(2023, 5));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.ClickFastNext();
|
||||||
|
Assert.Equal(5, calendarView.ContextDate.Month);
|
||||||
|
Assert.Equal(2024, calendarView.ContextDate.Year);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnNext_YearMode_UpdatesToNextYear()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var calendarView = new CalendarView
|
||||||
|
{ Mode = CalendarViewMode.Year };
|
||||||
|
window.Content = calendarView;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.SyncContextDate(new CalendarContext(2023, 5));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.ClickNext();
|
||||||
|
Assert.Equal(5, calendarView.ContextDate.Month);
|
||||||
|
Assert.Equal(2024, calendarView.ContextDate.Year);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnNext_DecadeMode_UpdatesToNextDecade()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var calendarView = new CalendarView { Mode = CalendarViewMode.Decade };
|
||||||
|
window.Content = calendarView;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.SyncContextDate(new CalendarContext(null, null, 2000, 2009));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.ClickNext();
|
||||||
|
Assert.Equal(2010, calendarView.ContextDate.StartYear);
|
||||||
|
Assert.Equal(2019, calendarView.ContextDate.EndYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnNext_CenturyMode_UpdatesToNextCentury()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var calendarView = new CalendarView { Mode = CalendarViewMode.Century };
|
||||||
|
window.Content = calendarView;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.SyncContextDate(new CalendarContext(null, null, 1900, 1999));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.ClickNext();
|
||||||
|
Assert.Equal(2000, calendarView.ContextDate.StartYear);
|
||||||
|
Assert.Equal(2099, calendarView.ContextDate.EndYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnPrevious_MonthMode_UpdatesToPreviousMonth()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var calendarView = new CalendarView { Mode = CalendarViewMode.Month };
|
||||||
|
window.Content = calendarView;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.SyncContextDate(new CalendarContext(2023, 5));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.ClickPrevious();
|
||||||
|
Assert.Equal(4, calendarView.ContextDate.Month);
|
||||||
|
Assert.Equal(2023, calendarView.ContextDate.Year);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnPrevious_YearMode_UpdatesToPreviousYear()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var calendarView = new CalendarView { Mode = CalendarViewMode.Year };
|
||||||
|
window.Content = calendarView;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.SyncContextDate(new CalendarContext(2023, 5));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.ClickPrevious();
|
||||||
|
Assert.Equal(2022, calendarView.ContextDate.Year);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnPrevious_DecadeMode_UpdatesToPreviousDecade()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var calendarView = new CalendarView { Mode = CalendarViewMode.Decade };
|
||||||
|
window.Content = calendarView;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.SyncContextDate(new CalendarContext(null, null, 2000, 2009));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.ClickPrevious();
|
||||||
|
Assert.Equal(1990, calendarView.ContextDate.StartYear);
|
||||||
|
Assert.Equal(1999, calendarView.ContextDate.EndYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnPrevious_CenturyMode_UpdatesToPreviousCentury()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var calendarView = new CalendarView { Mode = CalendarViewMode.Century };
|
||||||
|
window.Content = calendarView;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.SyncContextDate(new CalendarContext(null, null, 1900, 1999));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.ClickPrevious();
|
||||||
|
Assert.Equal(1800, calendarView.ContextDate.StartYear);
|
||||||
|
Assert.Equal(1899, calendarView.ContextDate.EndYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnFastPrevious_MonthMode_UpdatesToPreviousYear()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var calendarView = new CalendarView { Mode = CalendarViewMode.Month };
|
||||||
|
window.Content = calendarView;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.SyncContextDate(new CalendarContext(2023, 5));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.ClickFastPrevious();
|
||||||
|
Assert.Equal(2022, calendarView.ContextDate.Year);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnHeaderButtonClick_YearMode_SwitchesToDecadeMode()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var calendarView = new CalendarView { Mode = CalendarViewMode.Year };
|
||||||
|
window.Content = calendarView;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.SyncContextDate(new CalendarContext(2023, 5));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.ClickHeaderButton();
|
||||||
|
Assert.Equal(CalendarViewMode.Decade, calendarView.Mode);
|
||||||
|
Assert.Equal(2020, calendarView.ContextDate.StartYear);
|
||||||
|
Assert.Equal(2029, calendarView.ContextDate.EndYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnHeaderButtonClick_DecadeMode_SwitchesToCenturyMode()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var calendarView = new CalendarView { Mode = CalendarViewMode.Decade };
|
||||||
|
window.Content = calendarView;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.SyncContextDate(new CalendarContext(null, null, 2000, 2009));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.ClickHeaderButton();
|
||||||
|
Assert.Equal(CalendarViewMode.Century, calendarView.Mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnYearItemSelected_CenturyMode_SwitchesToDecadeMode()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var calendarView = new CalendarView { Mode = CalendarViewMode.Century };
|
||||||
|
window.Content = calendarView;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.SyncContextDate(new CalendarContext(null, null, 1900, 1999));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
var yearButton = calendarView.FindDescendantOfType<CalendarYearButton>();
|
||||||
|
var position = yearButton?.TranslatePoint(new Point(6, 6), window);
|
||||||
|
Assert.NotNull(position);
|
||||||
|
window.MouseUp(position.Value, MouseButton.Left);
|
||||||
|
Assert.Equal(CalendarViewMode.Decade, calendarView.Mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnYearItemSelected_DecadeMode_SwitchesToYearMode()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var calendarView = new CalendarView { Mode = CalendarViewMode.Decade };
|
||||||
|
window.Content = calendarView;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.SyncContextDate(new CalendarContext(null, null, 2000, 2009));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
var yearButton = calendarView.FindDescendantOfType<CalendarYearButton>();
|
||||||
|
var position = yearButton?.TranslatePoint(new Point(6, 6), window);
|
||||||
|
Assert.NotNull(position);
|
||||||
|
window.MouseUp(position.Value, MouseButton.Left);
|
||||||
|
Assert.Equal(CalendarViewMode.Year, calendarView.Mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnYearItemSelected_YearMode_SwitchesToMonthMode()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var calendarView = new CalendarView { Mode = CalendarViewMode.Year };
|
||||||
|
window.Content = calendarView;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.SyncContextDate(new CalendarContext(2023, 5));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
var yearButton = calendarView.FindDescendantOfType<CalendarYearButton>();
|
||||||
|
var position = yearButton?.TranslatePoint(new Point(6, 6), window);
|
||||||
|
Assert.NotNull(position);
|
||||||
|
window.MouseUp(position.Value, MouseButton.Left);
|
||||||
|
Assert.Equal(CalendarViewMode.Month, calendarView.Mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Update_FirstDayOfWeek_UpdatesCalendar()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var calendarView = new CalendarView { Mode = CalendarViewMode.Month, FirstDayOfWeek = DayOfWeek.Sunday};
|
||||||
|
window.Content = calendarView;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
calendarView.SyncContextDate(new CalendarContext(2023, 5));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
var monthGrid = calendarView.GetTemplateChildren()
|
||||||
|
.FirstOrDefault(a => a is Grid && a.Name == CalendarView.PART_MonthGrid) as Grid;
|
||||||
|
Assert.NotNull(monthGrid);
|
||||||
|
var dayHeader = monthGrid.Children.OfType<TextBlock>().FirstOrDefault();
|
||||||
|
var dayButton = monthGrid.Children.OfType<CalendarDayButton>().FirstOrDefault();
|
||||||
|
Assert.NotNull(dayHeader);
|
||||||
|
Assert.NotNull(dayButton);
|
||||||
|
Assert.Equal('S', dayHeader.Text?[0]);
|
||||||
|
Assert.Equal(30, (dayButton.DataContext as DateTime?)?.Day);
|
||||||
|
|
||||||
|
calendarView.FirstDayOfWeek = DayOfWeek.Tuesday;
|
||||||
|
|
||||||
|
dayHeader = monthGrid.Children.OfType<TextBlock>().FirstOrDefault();
|
||||||
|
dayButton = monthGrid.Children.OfType<CalendarDayButton>().FirstOrDefault();
|
||||||
|
Assert.NotNull(dayHeader);
|
||||||
|
Assert.NotNull(dayButton);
|
||||||
|
Assert.Equal('T', dayHeader.Text?[0]);
|
||||||
|
Assert.Equal(25, (dayButton.DataContext as DateTime?)?.Day);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Headless;
|
||||||
|
using Avalonia.Headless.XUnit;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Ursa.Controls;
|
||||||
|
|
||||||
|
namespace HeadlessTest.Ursa.Controls.DateTimePicker;
|
||||||
|
|
||||||
|
public class CalendarYearButtonTests
|
||||||
|
{
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void SetContext_YearMode_SetsContentToAbbreviatedMonthName()
|
||||||
|
{
|
||||||
|
var button = new CalendarYearButton();
|
||||||
|
var context = new CalendarContext(null, 5);
|
||||||
|
button.SetContext(CalendarViewMode.Year, context);
|
||||||
|
Assert.Equal("May", button.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void SetContext_YearMode_SetsContentToAbbreviatedMonthName_Chinese()
|
||||||
|
{
|
||||||
|
CultureInfo.CurrentCulture = new CultureInfo("zh-CN");
|
||||||
|
var button = new CalendarYearButton();
|
||||||
|
var context = new CalendarContext(null, 5);
|
||||||
|
button.SetContext(CalendarViewMode.Year, context);
|
||||||
|
Assert.Equal("5月", button.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void SetContext_DecadeMode_SetsContentToYear()
|
||||||
|
{
|
||||||
|
var button = new CalendarYearButton();
|
||||||
|
var context = new CalendarContext(2023);
|
||||||
|
button.SetContext(CalendarViewMode.Decade, context);
|
||||||
|
Assert.Equal("2023", button.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void SetContext_CenturyMode_SetsContentToYearRange()
|
||||||
|
{
|
||||||
|
var button = new CalendarYearButton();
|
||||||
|
var context = new CalendarContext(null, null, 1900, 1999);
|
||||||
|
button.SetContext(CalendarViewMode.Century, context);
|
||||||
|
Assert.Equal("1900-1999", button.Content);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void SetContext_InvalidYear_DisablesButton()
|
||||||
|
{
|
||||||
|
var button = new CalendarYearButton();
|
||||||
|
var context = new CalendarContext(null, null, -1);
|
||||||
|
button.SetContext(CalendarViewMode.Decade, context);
|
||||||
|
Assert.False(button.IsEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void SetContext_Month_Mode_DisablesButton()
|
||||||
|
{
|
||||||
|
var button = new CalendarYearButton();
|
||||||
|
var context = new CalendarContext(1, 1);
|
||||||
|
button.SetContext(CalendarViewMode.Month, context);
|
||||||
|
Assert.False(button.IsEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void OnPointerReleased_RaisesItemSelectedEvent()
|
||||||
|
{
|
||||||
|
|
||||||
|
var button = new CalendarYearButton();
|
||||||
|
var context = new CalendarContext(2023, 5);
|
||||||
|
button.SetContext(CalendarViewMode.Year, context);
|
||||||
|
int eventRaised = 0;
|
||||||
|
CalendarContext? eventContext = null;
|
||||||
|
void OnItemSelected( object? sender, CalendarYearButtonEventArgs args)
|
||||||
|
{
|
||||||
|
eventRaised++;
|
||||||
|
eventContext = args.Context;
|
||||||
|
}
|
||||||
|
button.ItemSelected += OnItemSelected;
|
||||||
|
Window window = new Window();
|
||||||
|
window.Content = button;
|
||||||
|
window.Show();
|
||||||
|
window.MouseUp(new Point(10, 10), MouseButton.Left);
|
||||||
|
Assert.Equal(1, eventRaised);
|
||||||
|
Assert.Equal(context, eventContext);
|
||||||
|
button.ItemSelected -= OnItemSelected;
|
||||||
|
eventContext = null;
|
||||||
|
window.MouseUp(new Point(10, 10), MouseButton.Left);
|
||||||
|
Assert.Null(eventContext);
|
||||||
|
Assert.Equal(1, eventRaised);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,355 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Headless;
|
||||||
|
using Avalonia.Headless.XUnit;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.LogicalTree;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
using HeadlessTest.Ursa.TestHelpers;
|
||||||
|
using Ursa.Controls;
|
||||||
|
using DatePicker = Ursa.Controls.DatePicker;
|
||||||
|
|
||||||
|
namespace HeadlessTest.Ursa.Controls.DateTimePicker;
|
||||||
|
|
||||||
|
public class DatePickerTests
|
||||||
|
{
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Click_Opens_Popup()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var picker = new DatePicker()
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top
|
||||||
|
};
|
||||||
|
window.Content = picker;
|
||||||
|
window.Show();
|
||||||
|
Assert.False(picker.IsDropdownOpen);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
window.MouseDown(new Point(10, 10), MouseButton.Left);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.True(picker.IsDropdownOpen);
|
||||||
|
|
||||||
|
window.MouseDown(new Point(10, 10), MouseButton.Left);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.True(picker.IsDropdownOpen);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Click_Button_Toggles_Popup()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var picker = new DatePicker()
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top
|
||||||
|
};
|
||||||
|
window.Content = picker;
|
||||||
|
window.Show();
|
||||||
|
|
||||||
|
var button = picker.GetTemplateChildOfType<Button>(DatePicker.PART_Button);
|
||||||
|
var position = button?.TranslatePoint(new Point(5, 5), window);
|
||||||
|
Assert.NotNull(position);
|
||||||
|
|
||||||
|
Assert.False(picker.IsDropdownOpen);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
window.MouseDown(position.Value, MouseButton.Left);
|
||||||
|
window.MouseUp(position.Value, MouseButton.Left);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.True(picker.IsDropdownOpen);
|
||||||
|
|
||||||
|
window.MouseDown(position.Value, MouseButton.Left);
|
||||||
|
window.MouseUp(position.Value, MouseButton.Left);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.False(picker.IsDropdownOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Clear_Set_SelectedDate_To_Null()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var picker = new DatePicker()
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top
|
||||||
|
};
|
||||||
|
window.Content = picker;
|
||||||
|
window.Show();
|
||||||
|
picker.SelectedDate = DateTime.Now;
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.NotNull(picker.SelectedDate);
|
||||||
|
picker.Clear();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.Null(picker.SelectedDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Press_Escape_Closes_Popup()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var picker = new DatePicker()
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top
|
||||||
|
};
|
||||||
|
window.Content = picker;
|
||||||
|
window.Show();
|
||||||
|
Assert.False(picker.IsDropdownOpen);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
window.MouseDown(new Point(10, 10), MouseButton.Left);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.True(picker.IsDropdownOpen);
|
||||||
|
window.KeyPressQwerty(PhysicalKey.Escape, RawInputModifiers.None);
|
||||||
|
Assert.False(picker.IsDropdownOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Press_Down_Opens_Popup()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var picker = new DatePicker()
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top
|
||||||
|
};
|
||||||
|
window.Content = picker;
|
||||||
|
window.Show();
|
||||||
|
Assert.False(picker.IsDropdownOpen);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
window.MouseDown(new Point(10, 10), MouseButton.Left);
|
||||||
|
window.KeyPressQwerty(PhysicalKey.ArrowDown, RawInputModifiers.None);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.True(picker.IsDropdownOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Press_Tab_Closes_Popup()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var picker = new DatePicker()
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top
|
||||||
|
};
|
||||||
|
window.Content = new StackPanel()
|
||||||
|
{
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
picker,
|
||||||
|
new TextBox(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.Show();
|
||||||
|
Assert.False(picker.IsDropdownOpen);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
window.MouseDown(new Point(10, 10), MouseButton.Left);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.True(picker.IsDropdownOpen);
|
||||||
|
window.KeyPressQwerty(PhysicalKey.Tab, RawInputModifiers.None);
|
||||||
|
Assert.False(picker.IsDropdownOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void SelectedDate_Set_TextBox_Text()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var picker = new DatePicker()
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
DisplayFormat = "yyyy-MM-dd",
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top
|
||||||
|
};
|
||||||
|
window.Content = picker;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
window.MouseDown(new Point(10, 10), MouseButton.Left);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
var popup = picker.GetTemplateChildOfType<Popup>(DatePicker.PART_Popup);
|
||||||
|
var calendar = popup.GetLogicalDescendants().OfType<CalendarView>().FirstOrDefault();
|
||||||
|
calendar?.RaiseEvent(new CalendarDayButtonEventArgs(new DateTime(2025, 2, 17))
|
||||||
|
{ RoutedEvent = CalendarView.DateSelectedEvent });
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
var textBox = picker.GetTemplateChildOfType<TextBox>(DatePicker.PART_TextBox);
|
||||||
|
Assert.NotNull(textBox);
|
||||||
|
Assert.Equal("2025-02-17", textBox.Text);
|
||||||
|
Assert.False(picker.IsDropdownOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Set_SelectedDate_To_Null_Clears_TextBox()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var picker = new DatePicker()
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
DisplayFormat = "yyyy-MM-dd",
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top
|
||||||
|
};
|
||||||
|
window.Content = picker;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
window.MouseDown(new Point(10, 10), MouseButton.Left);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
var popup = picker.GetTemplateChildOfType<Popup>(DatePicker.PART_Popup);
|
||||||
|
var calendar = popup.GetLogicalDescendants().OfType<CalendarView>().FirstOrDefault();
|
||||||
|
calendar?.RaiseEvent(new CalendarDayButtonEventArgs(new DateTime(2025, 2, 17))
|
||||||
|
{ RoutedEvent = CalendarView.DateSelectedEvent });
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
var textBox = picker.GetTemplateChildOfType<TextBox>(DatePicker.PART_TextBox);
|
||||||
|
Assert.NotNull(textBox);
|
||||||
|
Assert.Equal("2025-02-17", textBox.Text);
|
||||||
|
Assert.False(picker.IsDropdownOpen);
|
||||||
|
picker.SelectedDate = null;
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.Null(textBox.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Set_SelectedDate_Updates_TextBox()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var picker = new DatePicker()
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
DisplayFormat = "yyyy-MM-dd",
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top
|
||||||
|
};
|
||||||
|
window.Content = picker;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
var textBox = picker.GetTemplateChildOfType<TextBox>(DatePicker.PART_TextBox);
|
||||||
|
picker.SelectedDate = new DateTime(2025, 2, 18);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.Equal("2025-02-18", textBox?.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Set_Valid_TextBox_Text_Updates_SelectedDate()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var picker = new DatePicker()
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
DisplayFormat = "yyyy-MM-dd",
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top
|
||||||
|
};
|
||||||
|
window.Content = picker;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
var textBox = picker.GetTemplateChildOfType<TextBox>(DatePicker.PART_TextBox);
|
||||||
|
textBox?.RaiseEvent(new TextChangedEventArgs(TextBox.TextChangedEvent));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.Null(picker.SelectedDate);
|
||||||
|
textBox?.SetValue(TextBox.TextProperty, "2025-02-18");
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.Equal(new DateTime(2025, 2, 18), picker.SelectedDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Set_Invalid_TextBox_Text_Clears_SelectedDate()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var picker = new DatePicker()
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
DisplayFormat = "yyyy-MM-dd",
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top
|
||||||
|
};
|
||||||
|
window.Content = picker;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
var textBox = picker.GetTemplateChildOfType<TextBox>(DatePicker.PART_TextBox);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.Null(picker.SelectedDate);
|
||||||
|
textBox?.SetValue(TextBox.TextProperty, "2025-02-18");
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.Equal(new DateTime(2025, 2, 18), picker.SelectedDate);
|
||||||
|
textBox?.SetValue(TextBox.TextProperty, "2025-02-18-");
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.Null(picker.SelectedDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Empty_DisplayFormat_Works()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var picker = new DatePicker()
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
DisplayFormat = null,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top
|
||||||
|
};
|
||||||
|
window.Content = picker;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
var textBox = picker.GetTemplateChildOfType<TextBox>(DatePicker.PART_TextBox);
|
||||||
|
textBox?.RaiseEvent(new TextChangedEventArgs(TextBox.TextChangedEvent));
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.Null(picker.SelectedDate);
|
||||||
|
textBox?.SetValue(TextBox.TextProperty, "2025-02-18");
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.Equal(new DateTime(2025, 2, 18), picker.SelectedDate);
|
||||||
|
|
||||||
|
textBox?.SetValue(TextBox.TextProperty, "2025/02/19");
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.Equal(new DateTime(2025, 2, 19), picker.SelectedDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Ensure_Focusable()
|
||||||
|
{
|
||||||
|
var picker = new DatePicker();
|
||||||
|
Assert.True(picker.Focusable);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Click_On_Popup_Will_Not_Close_Popup()
|
||||||
|
{
|
||||||
|
var window = new Window()
|
||||||
|
{
|
||||||
|
Width = 800, Height = 800
|
||||||
|
};
|
||||||
|
var picker = new DatePicker()
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top
|
||||||
|
};
|
||||||
|
window.Content = picker;
|
||||||
|
window.Show();
|
||||||
|
Assert.False(picker.IsDropdownOpen);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
window.MouseDown(new Point(10, 10), MouseButton.Left);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.True(picker.IsDropdownOpen);
|
||||||
|
var popup = picker.GetTemplateChildOfType<Popup>(DatePicker.PART_Popup);
|
||||||
|
var calendar = popup?.GetLogicalDescendants().OfType<CalendarView>().FirstOrDefault();
|
||||||
|
Assert.NotNull(calendar);
|
||||||
|
var nextButton = calendar.GetTemplateChildOfType<Button>(CalendarView.PART_NextButton);
|
||||||
|
Assert.NotNull(nextButton);
|
||||||
|
var position = nextButton.TranslatePoint(new Point(5, 5), window);
|
||||||
|
Assert.NotNull(position);
|
||||||
|
window.MouseDown(new Point(10, 10), MouseButton.Left);
|
||||||
|
var renderRoot = popup.GetVisualRoot();
|
||||||
|
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.True(picker.IsDropdownOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Controls.Templates;
|
||||||
|
using Avalonia.Headless.XUnit;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using TimePickerPresenter = Ursa.Controls.TimePickerPresenter;
|
||||||
|
|
||||||
|
namespace HeadlessTest.Ursa.Controls.DateTimePicker;
|
||||||
|
|
||||||
|
public class TimePickerPresenterTests
|
||||||
|
{
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void TimePickerPresenter_DefaultValues_ShouldBeCorrect()
|
||||||
|
{
|
||||||
|
var presenter = new TimePickerPresenter();
|
||||||
|
|
||||||
|
Assert.False(presenter.NeedsConfirmation);
|
||||||
|
Assert.Equal(1, presenter.MinuteIncrement);
|
||||||
|
Assert.Equal(1, presenter.SecondIncrement);
|
||||||
|
Assert.Null(presenter.TimeHolder);
|
||||||
|
Assert.Equal("HH mm ss t", presenter.PanelFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void TimePickerPresenter_SetTime_ShouldUpdateTimeProperty()
|
||||||
|
{
|
||||||
|
var presenter = new TimePickerPresenter();
|
||||||
|
var time = new TimeSpan(10, 30, 45);
|
||||||
|
|
||||||
|
presenter.TimeHolder = time;
|
||||||
|
|
||||||
|
Assert.Equal(time, presenter.TimeHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaTheory]
|
||||||
|
[InlineData("hh mm ss", 0, 2, 4, 0, true, true, true, false)]
|
||||||
|
[InlineData("hh mm ss t", 0, 2, 4, 6, true, true, true, true)]
|
||||||
|
[InlineData("hh mm", 0, 2, 0, 0, true, true, false, false)]
|
||||||
|
[InlineData("mm ss", 0, 0, 2, 0, false, true, true, false)]
|
||||||
|
[InlineData("ss", 0, 0, 0, 0, false, false, true, false)]
|
||||||
|
[InlineData("t", 0, 0, 0, 0, false, false, false, true)]
|
||||||
|
public void TimePickerPresenter_SetPanelFormat_ShouldUpdatePanelLayout(
|
||||||
|
string format, int hourColumn, int minuteColumn, int secondColumn, int amColumn,
|
||||||
|
bool isHourPanelVisible, bool isMinutePanelVisible, bool isSecondPanelVisible, bool isAmPanelVisible
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var presenter = new TimePickerPresenter();
|
||||||
|
window.Content = presenter;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
presenter.PanelFormat = format;
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
|
||||||
|
Assert.Equal(format, presenter.PanelFormat);
|
||||||
|
|
||||||
|
var hourPanel = presenter.GetTemplateChildren().FirstOrDefault(a => a.Name == TimePickerPresenter.PART_HourScrollPanel);
|
||||||
|
var minutePanel = presenter.GetTemplateChildren().FirstOrDefault(a => a.Name == TimePickerPresenter.PART_MinuteScrollPanel);
|
||||||
|
var secondPanel = presenter.GetTemplateChildren().FirstOrDefault(a => a.Name == TimePickerPresenter.PART_SecondScrollPanel);
|
||||||
|
var amPanel = presenter.GetTemplateChildren().FirstOrDefault(a => a.Name == TimePickerPresenter.PART_AmPmScrollPanel);
|
||||||
|
|
||||||
|
Assert.NotNull(hourPanel);
|
||||||
|
Assert.NotNull(minutePanel);
|
||||||
|
Assert.NotNull(secondPanel);
|
||||||
|
Assert.NotNull(amPanel);
|
||||||
|
|
||||||
|
Assert.Equal(hourColumn, Grid.GetColumn(hourPanel));
|
||||||
|
Assert.Equal(minuteColumn, Grid.GetColumn(minutePanel));
|
||||||
|
Assert.Equal(secondColumn, Grid.GetColumn(secondPanel));
|
||||||
|
Assert.Equal(amColumn, Grid.GetColumn(amPanel));
|
||||||
|
|
||||||
|
Assert.Equal(isHourPanelVisible, hourPanel.IsVisible);
|
||||||
|
Assert.Equal(isMinutePanelVisible, minutePanel.IsVisible);
|
||||||
|
Assert.Equal(isSecondPanelVisible, secondPanel.IsVisible);
|
||||||
|
Assert.Equal(isAmPanelVisible, amPanel.IsVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void TimePickerPresenter_Confirm_ShouldSetTimeProperty()
|
||||||
|
{
|
||||||
|
var presenter = new TimePickerPresenter { NeedsConfirmation = true };
|
||||||
|
var time = new TimeSpan(10, 30, 45);
|
||||||
|
var eventRaised = 0;
|
||||||
|
TimeSpan? eventResult = null;
|
||||||
|
presenter.SelectedTimeChanged += (o, e) =>
|
||||||
|
{
|
||||||
|
eventRaised++;
|
||||||
|
eventResult = e.NewTime;
|
||||||
|
};
|
||||||
|
presenter.TimeHolder = time;
|
||||||
|
Assert.Null(eventResult);
|
||||||
|
Assert.Equal(0, eventRaised);
|
||||||
|
|
||||||
|
presenter.Confirm();
|
||||||
|
|
||||||
|
Assert.Equal(time, presenter.TimeHolder);
|
||||||
|
Assert.Equal(time, eventResult);
|
||||||
|
Assert.Equal(1, eventRaised);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void TimePickerPresenter_SyncTime_Should_Not_RaiseEvent()
|
||||||
|
{
|
||||||
|
var presenter = new TimePickerPresenter();
|
||||||
|
var oldTime = new TimeSpan(10, 30, 45);
|
||||||
|
var newTime = new TimeSpan(11, 45, 30);
|
||||||
|
presenter.SyncTime(oldTime);
|
||||||
|
var eventRaised = false;
|
||||||
|
presenter.SelectedTimeChanged += (sender, args) =>
|
||||||
|
{
|
||||||
|
eventRaised = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
presenter.SyncTime(newTime);
|
||||||
|
Assert.False(eventRaised);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaTheory]
|
||||||
|
[MemberData(nameof(GetSelectionMemberData))]
|
||||||
|
public void TimePickerPresenter_Time_Updated_When_Panel_Selection_Changed(string format, int hourSelection, int minuteSelection, int secondSelection, int amSelection, TimeSpan expectedTime)
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var presenter = new TimePickerPresenter();
|
||||||
|
window.Content = presenter;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
presenter.PanelFormat = format;
|
||||||
|
TimeSpan? eventResult = null;
|
||||||
|
presenter.SelectedTimeChanged += (o, e) =>
|
||||||
|
{
|
||||||
|
eventResult = e.NewTime;
|
||||||
|
};
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
|
||||||
|
var hourPanel = presenter.GetTemplateChildren().OfType<DateTimePickerPanel>().FirstOrDefault(a => a.Name == TimePickerPresenter.PART_HourSelector);
|
||||||
|
var minutePanel = presenter.GetTemplateChildren().OfType<DateTimePickerPanel>().FirstOrDefault(a => a.Name == TimePickerPresenter.PART_MinuteSelector);
|
||||||
|
var secondPanel = presenter.GetTemplateChildren().OfType<DateTimePickerPanel>().FirstOrDefault(a => a.Name == TimePickerPresenter.PART_SecondSelector);
|
||||||
|
var amPanel = presenter.GetTemplateChildren().OfType<DateTimePickerPanel>().FirstOrDefault(a => a.Name == TimePickerPresenter.PART_AmPmSelector);
|
||||||
|
|
||||||
|
Assert.NotNull(hourPanel);
|
||||||
|
Assert.NotNull(minutePanel);
|
||||||
|
Assert.NotNull(secondPanel);
|
||||||
|
Assert.NotNull(amPanel);
|
||||||
|
|
||||||
|
hourPanel.SelectedValue = hourSelection;
|
||||||
|
minutePanel.SelectedValue = minuteSelection;
|
||||||
|
secondPanel.SelectedValue = secondSelection;
|
||||||
|
amPanel.SelectedValue = amSelection;
|
||||||
|
|
||||||
|
Assert.Equal(expectedTime, eventResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaTheory]
|
||||||
|
[MemberData(nameof(GetSelectionMemberData))]
|
||||||
|
public void TimePickerPresenter_TimeHolder_Updated_When_Panel_Selection_Changed(string format, int hourSelection, int minuteSelection, int secondSelection, int amSelection, TimeSpan expectedTime)
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var presenter = new TimePickerPresenter(){NeedsConfirmation = true};
|
||||||
|
window.Content = presenter;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
presenter.PanelFormat = format;
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
|
||||||
|
var hourPanel = presenter.GetTemplateChildren().OfType<DateTimePickerPanel>().FirstOrDefault(a => a.Name == TimePickerPresenter.PART_HourSelector);
|
||||||
|
var minutePanel = presenter.GetTemplateChildren().OfType<DateTimePickerPanel>().FirstOrDefault(a => a.Name == TimePickerPresenter.PART_MinuteSelector);
|
||||||
|
var secondPanel = presenter.GetTemplateChildren().OfType<DateTimePickerPanel>().FirstOrDefault(a => a.Name == TimePickerPresenter.PART_SecondSelector);
|
||||||
|
var amPanel = presenter.GetTemplateChildren().OfType<DateTimePickerPanel>().FirstOrDefault(a => a.Name == TimePickerPresenter.PART_AmPmSelector);
|
||||||
|
|
||||||
|
Assert.NotNull(hourPanel);
|
||||||
|
Assert.NotNull(minutePanel);
|
||||||
|
Assert.NotNull(secondPanel);
|
||||||
|
Assert.NotNull(amPanel);
|
||||||
|
|
||||||
|
hourPanel.SelectedValue = hourSelection;
|
||||||
|
minutePanel.SelectedValue = minuteSelection;
|
||||||
|
secondPanel.SelectedValue = secondSelection;
|
||||||
|
amPanel.SelectedValue = amSelection;
|
||||||
|
|
||||||
|
Assert.Equal(expectedTime, presenter.TimeHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> GetSelectionMemberData()
|
||||||
|
{
|
||||||
|
yield return new object[] { "hh mm ss t", 1, 1, 1, 1, new TimeSpan(0, 13, 1, 1) };
|
||||||
|
yield return new object[] { "HH mm ss t", 12, 30, 45, 0, new TimeSpan(0, 12,30,45)};
|
||||||
|
yield return new object[] { "HH mm ss t", 12, 0, 0, 1, new TimeSpan(0, 12,0,0)};
|
||||||
|
yield return new object[] { "HH mm ss t", 13, 0, 0, 1, new TimeSpan(0, 13,0,0)};
|
||||||
|
yield return new object[] { "hh mm ss t", 9, 0, 0, 0, new TimeSpan(0, 9,0,0)};
|
||||||
|
yield return new object[] { "hh mm ss t", 9, 0, 0, 1, new TimeSpan(0, 21,0,0)};
|
||||||
|
yield return new object[] { "HH mm ss t", 0, 0, 0, 0, new TimeSpan(0, 0,0,0)};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Headless;
|
||||||
|
using Avalonia.Headless.XUnit;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using HeadlessTest.Ursa.TestHelpers;
|
||||||
|
using TimePicker = Ursa.Controls.TimePicker;
|
||||||
|
|
||||||
|
namespace HeadlessTest.Ursa.Controls.DateTimePicker;
|
||||||
|
|
||||||
|
public class TimePickerTests
|
||||||
|
{
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Click_Opens_Popup()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var timePicker = new TimePicker()
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top,
|
||||||
|
};
|
||||||
|
window.Content = timePicker;
|
||||||
|
window.Show();
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.False(timePicker.IsDropdownOpen);
|
||||||
|
window.MouseDown(new Point(10, 10), MouseButton.Left);
|
||||||
|
Assert.True(timePicker.IsDropdownOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
[AvaloniaFact]
|
||||||
|
public void Click_Button_Toggles_Popup()
|
||||||
|
{
|
||||||
|
var window = new Window();
|
||||||
|
var picker = new TimePicker()
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top
|
||||||
|
};
|
||||||
|
window.Content = picker;
|
||||||
|
window.Show();
|
||||||
|
|
||||||
|
var button = picker.GetTemplateChildOfType<Button>(TimePicker.PART_Button);
|
||||||
|
var position = button?.TranslatePoint(new Point(5, 5), window);
|
||||||
|
Assert.NotNull(position);
|
||||||
|
|
||||||
|
Assert.False(picker.IsDropdownOpen);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
window.MouseDown(position.Value, MouseButton.Left);
|
||||||
|
window.MouseUp(position.Value, MouseButton.Left);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.True(picker.IsDropdownOpen);
|
||||||
|
|
||||||
|
window.MouseDown(position.Value, MouseButton.Left);
|
||||||
|
window.MouseUp(position.Value, MouseButton.Left);
|
||||||
|
Dispatcher.UIThread.RunJobs();
|
||||||
|
Assert.False(picker.IsDropdownOpen);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,13 @@ public class DrawerCloseTestPopupControlVM : IDialogContext
|
|||||||
RequestClose?.Invoke(this, Result);
|
RequestClose?.Invoke(this, Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if NET8_0
|
||||||
public int Result { get; } = Random.Shared.Next();
|
public int Result { get; } = Random.Shared.Next();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if NETSTANDARD2_0
|
||||||
|
private static Random r = new Random();
|
||||||
|
public int Result { get; } = r.Next();
|
||||||
|
#endif
|
||||||
public event EventHandler<object?>? RequestClose;
|
public event EventHandler<object?>? RequestClose;
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<IsTestProject>true</IsTestProject>
|
<IsTestProject>true</IsTestProject>
|
||||||
|
|||||||
17
tests/HeadlessTest.Ursa/TestHelpers/ControlExtensions.cs
Normal file
17
tests/HeadlessTest.Ursa/TestHelpers/ControlExtensions.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Controls.Templates;
|
||||||
|
|
||||||
|
namespace HeadlessTest.Ursa.TestHelpers;
|
||||||
|
|
||||||
|
public static class ControlExtensions
|
||||||
|
{
|
||||||
|
public static IEnumerable<T> GetTemplateChildrenOfType<T>(this TemplatedControl control) where T: Control
|
||||||
|
{
|
||||||
|
return control.GetTemplateChildren().OfType<T>();
|
||||||
|
}
|
||||||
|
public static T? GetTemplateChildOfType<T>(this TemplatedControl control, string name) where T : Control
|
||||||
|
{
|
||||||
|
return control.GetTemplateChildren().OfType<T>().FirstOrDefault(a => a.Name == name);
|
||||||
|
}
|
||||||
|
}
|
||||||
122
tests/Test.Ursa/DateTimePickerTests/CalendarContextTests.cs
Normal file
122
tests/Test.Ursa/DateTimePickerTests/CalendarContextTests.cs
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
using Ursa.Controls;
|
||||||
|
|
||||||
|
namespace Test.Ursa.DateTimePicker;
|
||||||
|
|
||||||
|
public class CalendarContextTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Clone_ReturnsExactCopy()
|
||||||
|
{
|
||||||
|
var context = new CalendarContext(2023, 5, 2000, 2030);
|
||||||
|
var clone = context.Clone();
|
||||||
|
Assert.Equal(context.Year, clone.Year);
|
||||||
|
Assert.Equal(context.Month, clone.Month);
|
||||||
|
Assert.Equal(context.StartYear, clone.StartYear);
|
||||||
|
Assert.Equal(context.EndYear, clone.EndYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Today_ReturnsCurrentDate()
|
||||||
|
{
|
||||||
|
var today = CalendarContext.Today();
|
||||||
|
Assert.Equal(DateTime.Today.Year, today.Year);
|
||||||
|
Assert.Equal(DateTime.Today.Month, today.Month);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void With_UpdatesSpecifiedFields()
|
||||||
|
{
|
||||||
|
var context = new CalendarContext(2023, 5, 2000, 2030);
|
||||||
|
var updated = context.With(month: 6);
|
||||||
|
Assert.Equal(2023, updated.Year);
|
||||||
|
Assert.Equal(6, updated.Month);
|
||||||
|
Assert.Equal(2000, updated.StartYear);
|
||||||
|
Assert.Equal(2030, updated.EndYear);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NextMonth_UpdatesToNextMonth()
|
||||||
|
{
|
||||||
|
var context = new CalendarContext(2023, 5);
|
||||||
|
var nextMonth = context.NextMonth();
|
||||||
|
Assert.Equal(2023, nextMonth.Year);
|
||||||
|
Assert.Equal(6, nextMonth.Month);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NextMonth_UpdatesToNextYear()
|
||||||
|
{
|
||||||
|
var context = new CalendarContext(2023, 12);
|
||||||
|
var nextMonth = context.NextMonth();
|
||||||
|
Assert.Equal(2024, nextMonth.Year);
|
||||||
|
Assert.Equal(1, nextMonth.Month);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PreviousMonth_UpdatesToPreviousMonth()
|
||||||
|
{
|
||||||
|
var context = new CalendarContext(2023, 5);
|
||||||
|
var previousMonth = context.PreviousMonth();
|
||||||
|
Assert.Equal(2023, previousMonth.Year);
|
||||||
|
Assert.Equal(4, previousMonth.Month);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PreviousMonth_UpdatesToPreviousYear()
|
||||||
|
{
|
||||||
|
var context = new CalendarContext(2023, 1);
|
||||||
|
var previousMonth = context.PreviousMonth();
|
||||||
|
Assert.Equal(2022, previousMonth.Year);
|
||||||
|
Assert.Equal(12, previousMonth.Month);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NextYear_UpdatesToNextYear()
|
||||||
|
{
|
||||||
|
var context = new CalendarContext(2023, 5);
|
||||||
|
var nextYear = context.NextYear();
|
||||||
|
Assert.Equal(2024, nextYear.Year);
|
||||||
|
Assert.Equal(5, nextYear.Month);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PreviousYear_UpdatesToPreviousYear()
|
||||||
|
{
|
||||||
|
var context = new CalendarContext(2023, 5);
|
||||||
|
var previousYear = context.PreviousYear();
|
||||||
|
Assert.Equal(2022, previousYear.Year);
|
||||||
|
Assert.Equal(5, previousYear.Month);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CompareTo_SameContext_ReturnsZero()
|
||||||
|
{
|
||||||
|
var context1 = new CalendarContext(2023, 5);
|
||||||
|
var context2 = new CalendarContext(2023, 5);
|
||||||
|
Assert.Equal(0, context1.CompareTo(context2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CompareTo_DifferentYear_ReturnsNonZero()
|
||||||
|
{
|
||||||
|
var context1 = new CalendarContext(2023, 5);
|
||||||
|
var context2 = new CalendarContext(2024, 5);
|
||||||
|
Assert.NotEqual(0, context1.CompareTo(context2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CompareTo_DifferentMonth_ReturnsNonZero()
|
||||||
|
{
|
||||||
|
var context1 = new CalendarContext(2023, 5);
|
||||||
|
var context2 = new CalendarContext(2023, 6);
|
||||||
|
Assert.NotEqual(0, context1.CompareTo(context2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ToString_ReturnsCorrectFormat()
|
||||||
|
{
|
||||||
|
var context = new CalendarContext(2023, 5, 2000, 2030);
|
||||||
|
var expected = "Start: 2000, End: 2030, Year: 2023, Month: 5";
|
||||||
|
Assert.Equal(expected, context.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
112
tests/Test.Ursa/DateTimePickerTests/CalendarDayButtonTests.cs
Normal file
112
tests/Test.Ursa/DateTimePickerTests/CalendarDayButtonTests.cs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
using Avalonia.Input;
|
||||||
|
using Irihi.Avalonia.Shared.Common;
|
||||||
|
using CalendarDayButton = Ursa.Controls.CalendarDayButton;
|
||||||
|
|
||||||
|
namespace Test.Ursa.DateTimePicker;
|
||||||
|
|
||||||
|
public class CalendarDayButtonTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void IsToday_SetsPseudoClass()
|
||||||
|
{
|
||||||
|
var button = new CalendarDayButton();
|
||||||
|
button.IsToday = true;
|
||||||
|
Assert.Contains(CalendarDayButton.PC_Today, button.Classes);
|
||||||
|
Assert.True(button.IsToday);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsStartDate_SetsPseudoClass()
|
||||||
|
{
|
||||||
|
var button = new CalendarDayButton();
|
||||||
|
button.IsStartDate = true;
|
||||||
|
Assert.Contains(CalendarDayButton.PC_StartDate, button.Classes);
|
||||||
|
Assert.True(button.IsStartDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsEndDate_SetsPseudoClass()
|
||||||
|
{
|
||||||
|
var button = new CalendarDayButton();
|
||||||
|
button.IsEndDate = true;
|
||||||
|
Assert.Contains(CalendarDayButton.PC_EndDate, button.Classes);
|
||||||
|
Assert.True(button.IsEndDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsPreviewStartDate_SetsPseudoClass()
|
||||||
|
{
|
||||||
|
var button = new CalendarDayButton();
|
||||||
|
button.IsPreviewStartDate = true;
|
||||||
|
Assert.Contains(CalendarDayButton.PC_PreviewStartDate, button.Classes);
|
||||||
|
Assert.True(button.IsPreviewStartDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsPreviewEndDate_SetsPseudoClass()
|
||||||
|
{
|
||||||
|
var button = new CalendarDayButton();
|
||||||
|
button.IsPreviewEndDate = true;
|
||||||
|
Assert.Contains(CalendarDayButton.PC_PreviewEndDate, button.Classes);
|
||||||
|
Assert.True(button.IsPreviewEndDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsInRange_SetsPseudoClass()
|
||||||
|
{
|
||||||
|
var button = new CalendarDayButton();
|
||||||
|
button.IsInRange = true;
|
||||||
|
Assert.Contains(CalendarDayButton.PC_InRange, button.Classes);
|
||||||
|
Assert.True(button.IsInRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsSelected_SetsPseudoClass()
|
||||||
|
{
|
||||||
|
var button = new CalendarDayButton();
|
||||||
|
button.IsSelected = true;
|
||||||
|
Assert.Contains(PseudoClassName.PC_Selected, button.Classes);
|
||||||
|
Assert.True(button.IsSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsBlackout_SetsPseudoClass()
|
||||||
|
{
|
||||||
|
var button = new CalendarDayButton();
|
||||||
|
button.IsBlackout = true;
|
||||||
|
Assert.Contains(CalendarDayButton.PC_Blackout, button.Classes);
|
||||||
|
Assert.True(button.IsBlackout);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsNotCurrentMonth_SetsPseudoClass()
|
||||||
|
{
|
||||||
|
var button = new CalendarDayButton();
|
||||||
|
button.IsNotCurrentMonth = true;
|
||||||
|
Assert.Contains(CalendarDayButton.PC_NotCurrentMonth, button.Classes);
|
||||||
|
Assert.True(button.IsNotCurrentMonth);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResetSelection_ClearsPseudoClasses()
|
||||||
|
{
|
||||||
|
var button = new CalendarDayButton();
|
||||||
|
button.IsSelected = true;
|
||||||
|
button.IsStartDate = true;
|
||||||
|
button.ResetSelection();
|
||||||
|
Assert.DoesNotContain(PseudoClassName.PC_Selected, button.Classes);
|
||||||
|
Assert.DoesNotContain(CalendarDayButton.PC_StartDate, button.Classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsToday_ClearsOtherPseudoClasses()
|
||||||
|
{
|
||||||
|
var button = new CalendarDayButton();
|
||||||
|
button.IsStartDate = true;
|
||||||
|
button.IsEndDate = true;
|
||||||
|
button.IsToday = true;
|
||||||
|
Assert.Contains(CalendarDayButton.PC_Today, button.Classes);
|
||||||
|
Assert.Contains(CalendarDayButton.PC_EndDate, button.Classes);
|
||||||
|
Assert.DoesNotContain(CalendarDayButton.PC_StartDate, button.Classes);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using Ursa.Controls;
|
||||||
|
|
||||||
|
namespace Test.Ursa.DateTimePicker;
|
||||||
|
|
||||||
|
public class DateRangeExtensionTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Contains_DateInAnyRange_ShouldReturnTrue()
|
||||||
|
{
|
||||||
|
var ranges = new List<DateRange>
|
||||||
|
{
|
||||||
|
new(new DateTime(2023, 10, 1), new DateTime(2023, 10, 5)),
|
||||||
|
new(new DateTime(2023, 10, 10), new DateTime(2023, 10, 15))
|
||||||
|
};
|
||||||
|
var date = new DateTime(2023, 10, 3);
|
||||||
|
|
||||||
|
Assert.True(ranges.Contains(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Contains_DateNotInAnyRange_ShouldReturnFalse()
|
||||||
|
{
|
||||||
|
var ranges = new List<DateRange>
|
||||||
|
{
|
||||||
|
new(new DateTime(2023, 10, 1), new DateTime(2023, 10, 5)),
|
||||||
|
new(new DateTime(2023, 10, 10), new DateTime(2023, 10, 15))
|
||||||
|
};
|
||||||
|
var date = new DateTime(2023, 10, 6);
|
||||||
|
|
||||||
|
Assert.False(ranges.Contains(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Contains_NullDate_ShouldReturnFalse()
|
||||||
|
{
|
||||||
|
var ranges = new List<DateRange>
|
||||||
|
{
|
||||||
|
new(new DateTime(2023, 10, 1), new DateTime(2023, 10, 5)),
|
||||||
|
new(new DateTime(2023, 10, 10), new DateTime(2023, 10, 15))
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.False(ranges.Contains((DateTime?)null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Contains_NullRanges_ShouldReturnFalse()
|
||||||
|
{
|
||||||
|
IEnumerable<DateRange>? ranges = null;
|
||||||
|
var date = new DateTime(2023, 10, 3);
|
||||||
|
|
||||||
|
Assert.False(ranges.Contains(date));
|
||||||
|
}
|
||||||
|
}
|
||||||
64
tests/Test.Ursa/DateTimePickerTests/DateRangeTests.cs
Normal file
64
tests/Test.Ursa/DateTimePickerTests/DateRangeTests.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using Ursa.Controls;
|
||||||
|
|
||||||
|
namespace Test.Ursa.DateTimePicker;
|
||||||
|
|
||||||
|
public class DateRangeTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void DateRange_SingleDayRange_ShouldHaveSameStartAndEnd()
|
||||||
|
{
|
||||||
|
var date = new DateTime(2023, 10, 1);
|
||||||
|
var range = new DateRange(date);
|
||||||
|
|
||||||
|
Assert.Equal(date.Date, range.Start);
|
||||||
|
Assert.Equal(date.Date, range.End);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DateRange_ValidRange_ShouldSetStartAndEndCorrectly()
|
||||||
|
{
|
||||||
|
var start = new DateTime(2023, 10, 1);
|
||||||
|
var end = new DateTime(2023, 10, 5);
|
||||||
|
var range = new DateRange(start, end);
|
||||||
|
|
||||||
|
Assert.Equal(start.Date, range.Start);
|
||||||
|
Assert.Equal(end.Date, range.End);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DateRange_EndBeforeStart_ShouldSetEndToStart()
|
||||||
|
{
|
||||||
|
var start = new DateTime(2023, 10, 5);
|
||||||
|
var end = new DateTime(2023, 10, 1);
|
||||||
|
var range = new DateRange(start, end);
|
||||||
|
|
||||||
|
Assert.Equal(start.Date, range.Start);
|
||||||
|
Assert.Equal(start.Date, range.End);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Contains_DateWithinRange_ShouldReturnTrue()
|
||||||
|
{
|
||||||
|
var range = new DateRange(new DateTime(2023, 10, 1), new DateTime(2023, 10, 5));
|
||||||
|
var date = new DateTime(2023, 10, 3);
|
||||||
|
|
||||||
|
Assert.True(range.Contains(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Contains_DateOutsideRange_ShouldReturnFalse()
|
||||||
|
{
|
||||||
|
var range = new DateRange(new DateTime(2023, 10, 1), new DateTime(2023, 10, 5));
|
||||||
|
var date = new DateTime(2023, 10, 6);
|
||||||
|
|
||||||
|
Assert.False(range.Contains(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Contains_NullDate_ShouldReturnFalse()
|
||||||
|
{
|
||||||
|
var range = new DateRange(new DateTime(2023, 10, 1), new DateTime(2023, 10, 5));
|
||||||
|
|
||||||
|
Assert.False(range.Contains(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
74
tests/Test.Ursa/DateTimePickerTests/DateTimeHelperTests.cs
Normal file
74
tests/Test.Ursa/DateTimePickerTests/DateTimeHelperTests.cs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
using Ursa.Controls;
|
||||||
|
|
||||||
|
namespace Test.Ursa.DateTimePicker;
|
||||||
|
|
||||||
|
public class DateTimeHelperTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetFirstDayOfMonth_ReturnsFirstDay()
|
||||||
|
{
|
||||||
|
var date = new DateTime(2023, 5, 15);
|
||||||
|
var firstDay = date.GetFirstDayOfMonth();
|
||||||
|
Assert.Equal(new DateTime(2023, 5, 1), firstDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetLastDayOfMonth_ReturnsLastDay()
|
||||||
|
{
|
||||||
|
var date = new DateTime(2023, 5, 15);
|
||||||
|
var lastDay = date.GetLastDayOfMonth();
|
||||||
|
Assert.Equal(new DateTime(2023, 5, 31), lastDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CompareYearMonth_SameYearMonth_ReturnsZero()
|
||||||
|
{
|
||||||
|
var date1 = new DateTime(2023, 5, 15);
|
||||||
|
var date2 = new DateTime(2023, 5, 20);
|
||||||
|
var result = DateTimeHelper.CompareYearMonth(date1, date2);
|
||||||
|
Assert.Equal(0, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CompareYearMonth_DifferentYearMonth_ReturnsDifference()
|
||||||
|
{
|
||||||
|
var date1 = new DateTime(2023, 5, 15);
|
||||||
|
var date2 = new DateTime(2024, 6, 20);
|
||||||
|
var result = DateTimeHelper.CompareYearMonth(date1, date2);
|
||||||
|
Assert.Equal(-13, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Min_ReturnsMinimumDate()
|
||||||
|
{
|
||||||
|
var date1 = new DateTime(2023, 5, 15);
|
||||||
|
var date2 = new DateTime(2024, 6, 20);
|
||||||
|
var result = DateTimeHelper.Min(date1, date2);
|
||||||
|
Assert.Equal(date1, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Max_ReturnsMaximumDate()
|
||||||
|
{
|
||||||
|
var date1 = new DateTime(2023, 5, 15);
|
||||||
|
var date2 = new DateTime(2024, 6, 20);
|
||||||
|
var result = DateTimeHelper.Max(date1, date2);
|
||||||
|
Assert.Equal(date2, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetDecadeViewRangeByYear_ReturnsCorrectRange()
|
||||||
|
{
|
||||||
|
var year = 2023;
|
||||||
|
var result = DateTimeHelper.GetDecadeViewRangeByYear(year);
|
||||||
|
Assert.Equal((2020, 2029), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetCenturyViewRangeByYear_ReturnsCorrectRange()
|
||||||
|
{
|
||||||
|
var year = 2023;
|
||||||
|
var result = DateTimeHelper.GetCenturyViewRangeByYear(year);
|
||||||
|
Assert.Equal((2000, 2100), result);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user