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"
|
||||
x:Class="Ursa.Demo.Pages.DatePickerDemo">
|
||||
<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"/>
|
||||
<TextBlock Text="{Binding #singlePicker.SelectedDate}" ></TextBlock>
|
||||
<u:DatePicker Name="singlePicker" Width="200" Classes="ClearButton" />
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Avalonia.Metadata;
|
||||
|
||||
[assembly:InternalsVisibleTo("HeadlessTest.Ursa")]
|
||||
[assembly:InternalsVisibleTo("Test.Ursa")]
|
||||
[assembly:XmlnsPrefix("https://irihi.tech/ursa", "u")]
|
||||
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa")]
|
||||
[assembly:XmlnsDefinition("https://irihi.tech/ursa", "Ursa.Controls")]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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? Month { get; } = month;
|
||||
|
||||
@@ -95,13 +95,13 @@ public class CalendarView : TemplatedControl
|
||||
|
||||
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);
|
||||
|
||||
public CalendarContext ContextDate
|
||||
internal CalendarContext ContextDate
|
||||
{
|
||||
get => _contextDate;
|
||||
internal set => SetAndRaise(ContextDateProperty, ref _contextDate, value);
|
||||
set => SetAndRaise(ContextDateProperty, ref _contextDate, value);
|
||||
}
|
||||
|
||||
public bool IsTodayHighlighted
|
||||
@@ -535,7 +535,7 @@ public class CalendarView : TemplatedControl
|
||||
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;
|
||||
_end = endDate;
|
||||
|
||||
@@ -25,7 +25,7 @@ public class CalendarYearButton : ContentControl
|
||||
|
||||
internal CalendarViewMode Mode { get; private set; }
|
||||
|
||||
public event EventHandler<CalendarDayButtonEventArgs> ItemSelected
|
||||
public event EventHandler<CalendarYearButtonEventArgs> ItemSelected
|
||||
{
|
||||
add => AddHandler(ItemSelectedEvent, value);
|
||||
remove => RemoveHandler(ItemSelectedEvent, value);
|
||||
|
||||
@@ -7,8 +7,6 @@ using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.VisualTree;
|
||||
using Irihi.Avalonia.Shared.Common;
|
||||
using Irihi.Avalonia.Shared.Contracts;
|
||||
using Irihi.Avalonia.Shared.Helpers;
|
||||
|
||||
@@ -49,6 +47,7 @@ public class DatePicker: DatePickerBase, IClearControl
|
||||
|
||||
static DatePicker()
|
||||
{
|
||||
FocusableProperty.OverrideDefaultValue<DatePicker>(true);
|
||||
SelectedDateProperty.Changed.AddClassHandler<DatePicker, DateTime?>((picker, args) =>
|
||||
picker.OnSelectionChanged(args));
|
||||
}
|
||||
@@ -64,7 +63,6 @@ public class DatePicker: DatePickerBase, IClearControl
|
||||
|
||||
GotFocusEvent.RemoveHandler(OnTextBoxGetFocus, _textBox);
|
||||
TextBox.TextChangedEvent.RemoveHandler(OnTextChanged, _textBox);
|
||||
PointerPressedEvent.RemoveHandler(OnTextBoxPointerPressed, _textBox);
|
||||
Button.ClickEvent.RemoveHandler(OnButtonClick, _button);
|
||||
CalendarView.DateSelectedEvent.RemoveHandler(OnDateSelected, _calendar);
|
||||
|
||||
@@ -73,10 +71,9 @@ public class DatePicker: DatePickerBase, IClearControl
|
||||
_textBox = e.NameScope.Find<TextBox>(PART_TextBox);
|
||||
_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);
|
||||
TextBox.TextChangedEvent.AddHandler(OnTextChanged, _textBox);
|
||||
PointerPressedEvent.AddHandler(OnTextBoxPointerPressed, RoutingStrategies.Tunnel, false, _textBox);
|
||||
CalendarView.DateSelectedEvent.AddHandler(OnDateSelected, RoutingStrategies.Bubble, true, _calendar);
|
||||
SyncSelectedDateToText(SelectedDate);
|
||||
}
|
||||
@@ -89,19 +86,10 @@ public class DatePicker: DatePickerBase, IClearControl
|
||||
|
||||
private void OnButtonClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
Focus(NavigationMethod.Pointer);
|
||||
SetCurrentValue(IsDropdownOpenProperty, !IsDropdownOpen);
|
||||
}
|
||||
|
||||
private void OnTextBoxPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
if (_calendar is not null)
|
||||
if(IsFocused)
|
||||
{
|
||||
var date = SelectedDate ?? DateTime.Today;
|
||||
_calendar.ContextDate = new CalendarContext(date.Year, date.Month);
|
||||
_calendar.UpdateDayButtons();
|
||||
SetCurrentValue(IsDropdownOpenProperty, !IsDropdownOpen);
|
||||
}
|
||||
SetCurrentValue(IsDropdownOpenProperty, true);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -186,6 +174,7 @@ public class DatePicker: DatePickerBase, IClearControl
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
@@ -213,4 +202,40 @@ public class DatePicker: DatePickerBase, IClearControl
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Ursa.Controls;
|
||||
[TemplatePart(PART_Popup, typeof(Popup))]
|
||||
[TemplatePart(PART_TextBox, typeof(TextBox))]
|
||||
[TemplatePart(PART_Calendar, typeof(CalendarView))]
|
||||
[TemplatePart(PART_TimePicker, typeof(TimePicker))]
|
||||
[TemplatePart(PART_TimePicker, typeof(TimePickerPresenter))]
|
||||
public class DateTimePicker : DatePickerBase
|
||||
{
|
||||
public const string PART_Button = "PART_Button";
|
||||
@@ -76,6 +76,7 @@ public class DateTimePicker : DatePickerBase
|
||||
|
||||
private void OnSelectionChanged(AvaloniaPropertyChangedEventArgs<DateTime?> args)
|
||||
{
|
||||
if (_fromText) return;
|
||||
SyncSelectedDateToText(args.NewValue.Value);
|
||||
}
|
||||
|
||||
@@ -85,14 +86,14 @@ public class DateTimePicker : DatePickerBase
|
||||
{
|
||||
_textBox?.SetValue(TextBox.TextProperty, null);
|
||||
_calendar?.ClearSelection();
|
||||
_timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, null);
|
||||
_timePickerPresenter?.SyncTime(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
_textBox?.SetValue(TextBox.TextProperty,
|
||||
date.Value.ToString(DisplayFormat ?? CultureInfo.InvariantCulture.DateTimeFormat.FullDateTimePattern));
|
||||
_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;
|
||||
_calendar.ContextDate = new CalendarContext(date.Year, date.Month);
|
||||
_calendar.UpdateDayButtons();
|
||||
_timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, SelectedDate?.TimeOfDay);
|
||||
_timePickerPresenter?.SyncTime(SelectedDate?.TimeOfDay);
|
||||
}
|
||||
|
||||
SetCurrentValue(IsDropdownOpenProperty, true);
|
||||
}
|
||||
|
||||
private bool _fromText = false;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void OnTextChanged(object? sender, TextChangedEventArgs e)
|
||||
{
|
||||
@@ -189,11 +192,13 @@ public class DateTimePicker : DatePickerBase
|
||||
|
||||
private void SetSelectedDate(bool fromText = false)
|
||||
{
|
||||
var temp = _fromText;
|
||||
_fromText = fromText;
|
||||
if (string.IsNullOrEmpty(_textBox?.Text))
|
||||
{
|
||||
SetCurrentValue(SelectedDateProperty, null);
|
||||
_calendar?.ClearSelection();
|
||||
_timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, null);
|
||||
_timePickerPresenter?.SyncTime(null);
|
||||
}
|
||||
else if (DisplayFormat is null || DisplayFormat.Length == 0)
|
||||
{
|
||||
@@ -201,7 +206,7 @@ public class DateTimePicker : DatePickerBase
|
||||
{
|
||||
SetCurrentValue(SelectedDateProperty, defaultTime);
|
||||
_calendar?.MarkDates(defaultTime.Date, defaultTime.Date);
|
||||
_timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, defaultTime.TimeOfDay);
|
||||
_timePickerPresenter?.SyncTime(defaultTime.TimeOfDay);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -217,16 +222,17 @@ public class DateTimePicker : DatePickerBase
|
||||
}
|
||||
|
||||
_calendar?.MarkDates(date.Date, date.Date);
|
||||
_timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, date.TimeOfDay);
|
||||
_timePickerPresenter?.SyncTime(date.TimeOfDay);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetCurrentValue(SelectedDateProperty, null);
|
||||
if (!fromText) _textBox?.SetValue(TextBox.TextProperty, null);
|
||||
_calendar?.ClearSelection();
|
||||
_timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, null);
|
||||
_timePickerPresenter?.SyncTime(null);
|
||||
}
|
||||
}
|
||||
_fromText = temp;
|
||||
}
|
||||
|
||||
private void OnTextBoxGetFocus(object? sender, GotFocusEventArgs e)
|
||||
@@ -236,7 +242,7 @@ public class DateTimePicker : DatePickerBase
|
||||
var date = SelectedDate ?? DateTime.Today;
|
||||
_calendar.ContextDate = _calendar.ContextDate.With(date.Year, date.Month);
|
||||
_calendar.UpdateDayButtons();
|
||||
_timePickerPresenter?.SetValue(TimePickerPresenter.TimeProperty, date.TimeOfDay);
|
||||
_timePickerPresenter?.SyncTime(date.TimeOfDay);
|
||||
}
|
||||
|
||||
SetCurrentValue(IsDropdownOpenProperty, true);
|
||||
|
||||
@@ -29,24 +29,22 @@ public class TimePicker : TimePickerBase, IClearControl
|
||||
public static readonly StyledProperty<string?> WatermarkProperty = AvaloniaProperty.Register<TimePicker, string?>(
|
||||
nameof(Watermark));
|
||||
|
||||
private bool _suppressTextPresenterEvent;
|
||||
|
||||
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()
|
||||
{
|
||||
FocusableProperty.OverrideDefaultValue<TimePicker>(true);
|
||||
SelectedTimeProperty.Changed.AddClassHandler<TimePicker, TimeSpan?>((picker, args) =>
|
||||
picker.OnSelectionChanged(args));
|
||||
DisplayFormatProperty.Changed.AddClassHandler<TimePicker, string?>((picker, args) => picker.OnDisplayFormatChanged(args));
|
||||
}
|
||||
|
||||
private void OnDisplayFormatChanged(AvaloniaPropertyChangedEventArgs<string?> _)
|
||||
{
|
||||
if (_textBox is null) return;
|
||||
SyncTimeToText(SelectedTime);
|
||||
DisplayFormatProperty.Changed.AddClassHandler<TimePicker, string?>((picker, args) =>
|
||||
picker.OnDisplayFormatChanged(args));
|
||||
}
|
||||
|
||||
public string? Watermark
|
||||
@@ -63,58 +61,66 @@ public class TimePicker : TimePickerBase, IClearControl
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
SetCurrentValue(SelectedTimeProperty, null);
|
||||
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)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
|
||||
GotFocusEvent.RemoveHandler(OnTextBoxGetFocus, _textBox);
|
||||
TextBox.TextChangedEvent.RemoveHandler(OnTextChanged, _textBox);
|
||||
PointerPressedEvent.RemoveHandler(OnTextBoxPointerPressed, _textBox);
|
||||
Button.ClickEvent.RemoveHandler(OnButtonClick, _button);
|
||||
TimePickerPresenter.SelectedTimeChangedEvent.RemoveHandler(OnPresenterTimeChanged, _presenter);
|
||||
|
||||
_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);
|
||||
_button = e.NameScope.Find<Button>(PART_Button);
|
||||
|
||||
GotFocusEvent.AddHandler(OnTextBoxGetFocus, _textBox);
|
||||
TextBox.TextChangedEvent.AddHandler(OnTextChanged, _textBox);
|
||||
PointerPressedEvent.AddHandler(OnTextBoxPointerPressed, RoutingStrategies.Tunnel, false, _textBox);
|
||||
Button.ClickEvent.AddHandler(OnButtonClick, _button);
|
||||
TimePickerPresenter.SelectedTimeChangedEvent.AddHandler(OnPresenterTimeChanged, _presenter);
|
||||
|
||||
// SetCurrentValue(SelectedTimeProperty, DateTime.Now.TimeOfDay);
|
||||
_presenter?.SetValue(TimePickerPresenter.TimeProperty, SelectedTime);
|
||||
|
||||
_presenter?.SyncTime(SelectedTime);
|
||||
SyncTimeToText(SelectedTime);
|
||||
}
|
||||
|
||||
private void OnPresenterTimeChanged(object? sender, TimeChangedEventArgs e)
|
||||
{
|
||||
if (!IsInitialized) return;
|
||||
if (_suppressTextPresenterEvent) return;
|
||||
SetCurrentValue(SelectedTimeProperty, e.NewTime);
|
||||
}
|
||||
|
||||
private void OnButtonClick(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
Focus(NavigationMethod.Pointer);
|
||||
SetCurrentValue(IsDropdownOpenProperty, !IsDropdownOpen);
|
||||
}
|
||||
|
||||
private void OnTextBoxPointerPressed(object? sender, PointerPressedEventArgs e)
|
||||
{
|
||||
SetCurrentValue(IsDropdownOpenProperty, true);
|
||||
if (IsFocused) SetCurrentValue(IsDropdownOpenProperty, !IsDropdownOpen);
|
||||
}
|
||||
|
||||
private void OnTextBoxGetFocus(object? sender, GotFocusEventArgs e)
|
||||
{
|
||||
// SetCurrentValue(IsDropdownOpenProperty, true);
|
||||
SetCurrentValue(IsDropdownOpenProperty, true);
|
||||
}
|
||||
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Escape)
|
||||
@@ -123,20 +129,17 @@ public class TimePicker : TimePickerBase, IClearControl
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Key == Key.Down)
|
||||
{
|
||||
SetCurrentValue(IsDropdownOpenProperty, true);
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Key == Key.Tab)
|
||||
{
|
||||
SetCurrentValue(IsDropdownOpenProperty, false);
|
||||
return;
|
||||
}
|
||||
|
||||
base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
@@ -145,17 +148,17 @@ public class TimePicker : TimePickerBase, IClearControl
|
||||
{
|
||||
if (string.IsNullOrEmpty(_textBox?.Text))
|
||||
{
|
||||
TimePickerPresenter.TimeProperty.SetValue(null, _presenter);
|
||||
_presenter?.SyncTime(null);
|
||||
}
|
||||
else if (DisplayFormat is null || DisplayFormat.Length == 0)
|
||||
{
|
||||
if (TimeSpan.TryParse(_textBox?.Text, out var defaultTime))
|
||||
TimePickerPresenter.TimeProperty.SetValue(defaultTime, _presenter);
|
||||
if (TimeSpan.TryParse(_textBox?.Text, out var defaultTime)) _presenter?.SyncTime(defaultTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
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;
|
||||
_suppressTextPresenterEvent = true;
|
||||
_presenter?.SetValue(TimePickerPresenter.TimeProperty, args.NewValue.Value);
|
||||
_presenter?.SyncTime(args.NewValue.Value);
|
||||
SyncTimeToText(args.NewValue.Value);
|
||||
_suppressTextPresenterEvent = false;
|
||||
}
|
||||
@@ -176,6 +179,7 @@ public class TimePicker : TimePickerBase, IClearControl
|
||||
_textBox.Text = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var date = new DateTime(1, 1, 1, time.Value.Hours, time.Value.Minutes, time.Value.Seconds);
|
||||
var text = date.ToString(DisplayFormat);
|
||||
_textBox.Text = text;
|
||||
@@ -199,4 +203,31 @@ public class TimePicker : TimePickerBase, IClearControl
|
||||
base.UpdateDataValidation(property, state, 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.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Irihi.Avalonia.Shared.Helpers;
|
||||
|
||||
@@ -43,20 +44,20 @@ public class TimePickerPresenter : TemplatedControl
|
||||
|
||||
public static readonly StyledProperty<int> MinuteIncrementProperty =
|
||||
AvaloniaProperty.Register<TimePickerPresenter, int>(
|
||||
nameof(MinuteIncrement));
|
||||
nameof(MinuteIncrement), 1);
|
||||
|
||||
public static readonly StyledProperty<int> SecondIncrementProperty =
|
||||
AvaloniaProperty.Register<TimePickerPresenter, int>(
|
||||
nameof(SecondIncrement));
|
||||
|
||||
public static readonly StyledProperty<TimeSpan?> TimeProperty =
|
||||
AvaloniaProperty.Register<TimePickerPresenter, TimeSpan?>(
|
||||
nameof(Time));
|
||||
nameof(SecondIncrement), 1);
|
||||
|
||||
public static readonly StyledProperty<string> PanelFormatProperty =
|
||||
AvaloniaProperty.Register<TimePickerPresenter, string>(
|
||||
nameof(PanelFormat), "HH mm ss t");
|
||||
|
||||
public static readonly RoutedEvent<TimeChangedEventArgs> SelectedTimeChangedEvent =
|
||||
RoutedEvent.Register<TimePickerPresenter, TimeChangedEventArgs>(
|
||||
nameof(SelectedTimeChanged), RoutingStrategies.Bubble);
|
||||
|
||||
private Control? _ampmScrollPanel;
|
||||
private DateTimePickerPanel? _ampmSelector;
|
||||
private Control? _firstSeparator;
|
||||
@@ -65,26 +66,21 @@ public class TimePickerPresenter : TemplatedControl
|
||||
private DateTimePickerPanel? _hourSelector;
|
||||
private Control? _minuteScrollPanel;
|
||||
private DateTimePickerPanel? _minuteSelector;
|
||||
|
||||
private Grid? _pickerContainer;
|
||||
private Control? _secondScrollPanel;
|
||||
private DateTimePickerPanel? _secondSelector;
|
||||
private Control? _secondSeparator;
|
||||
|
||||
private bool _surpressTimeEvent = true;
|
||||
private Control? _thirdSeparator;
|
||||
internal TimeSpan TimeHolder;
|
||||
private bool _updateFromTimeChange;
|
||||
private bool _use12Clock;
|
||||
internal TimeSpan? TimeHolder;
|
||||
|
||||
static TimePickerPresenter()
|
||||
{
|
||||
PanelFormatProperty.Changed.AddClassHandler<TimePickerPresenter, string>((presenter, args) =>
|
||||
presenter.OnPanelFormatChanged(args));
|
||||
TimeProperty.Changed.AddClassHandler<TimePickerPresenter, TimeSpan?>((presenter, args) =>
|
||||
presenter.OnTimeChanged(args));
|
||||
}
|
||||
|
||||
public TimePickerPresenter()
|
||||
{
|
||||
// SetCurrentValue(TimeProperty, DateTime.Now.TimeOfDay);
|
||||
}
|
||||
|
||||
public bool NeedsConfirmation
|
||||
@@ -105,40 +101,18 @@ public class TimePickerPresenter : TemplatedControl
|
||||
set => SetValue(SecondIncrementProperty, value);
|
||||
}
|
||||
|
||||
public TimeSpan? Time
|
||||
{
|
||||
get => GetValue(TimeProperty);
|
||||
set => SetValue(TimeProperty, value);
|
||||
}
|
||||
|
||||
public string PanelFormat
|
||||
{
|
||||
get => GetValue(PanelFormatProperty);
|
||||
set => SetValue(PanelFormatProperty, value);
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent<TimeChangedEventArgs> SelectedTimeChangedEvent =
|
||||
RoutedEvent.Register<TimePickerPresenter, TimeChangedEventArgs>(
|
||||
nameof(SelectedTimeChanged), RoutingStrategies.Bubble);
|
||||
|
||||
|
||||
public event EventHandler<TimeChangedEventArgs> SelectedTimeChanged
|
||||
{
|
||||
add => AddHandler(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)
|
||||
{
|
||||
var format = args.NewValue.Value;
|
||||
@@ -180,6 +154,7 @@ public class TimePickerPresenter : TemplatedControl
|
||||
{
|
||||
panels.Add(_ampmScrollPanel);
|
||||
_ampmSelector?.SetValue(DateTimePickerPanel.ItemFormatProperty, part);
|
||||
if (_ampmSelector is not null) _ampmSelector.IsEnabled = _use12Clock;
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -191,6 +166,7 @@ public class TimePickerPresenter : TemplatedControl
|
||||
if (panels.Count < 1) return;
|
||||
IsVisibleProperty.SetValue(false, _hourScrollPanel, _minuteScrollPanel, _secondScrollPanel, _ampmScrollPanel,
|
||||
_firstSeparator, _secondSeparator, _thirdSeparator);
|
||||
Grid.ColumnProperty.SetValue(0, _hourScrollPanel, _minuteScrollPanel, _secondScrollPanel, _ampmScrollPanel);
|
||||
for (var i = 0; i < panels.Count; i++)
|
||||
{
|
||||
var panel = panels[i];
|
||||
@@ -211,47 +187,63 @@ public class TimePickerPresenter : TemplatedControl
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs 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);
|
||||
_minuteSelector = e.NameScope.Find<DateTimePickerPanel>(PART_MinuteSelector);
|
||||
_secondSelector = e.NameScope.Find<DateTimePickerPanel>(PART_SecondSelector);
|
||||
_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);
|
||||
_hourScrollPanel = e.NameScope.Find<Control>(PART_HourScrollPanel);
|
||||
_minuteScrollPanel = e.NameScope.Find<Control>(PART_MinuteScrollPanel);
|
||||
_secondScrollPanel = e.NameScope.Find<Control>(PART_SecondScrollPanel);
|
||||
_ampmScrollPanel = e.NameScope.Find<Control>(PART_AmPmScrollPanel);
|
||||
|
||||
_firstSeparator = e.NameScope.Find<Control>(PART_FirstSeparator);
|
||||
_secondSeparator = e.NameScope.Find<Control>(PART_SecondSeparator);
|
||||
_thirdSeparator = e.NameScope.Find<Control>(PART_ThirdSeparator);
|
||||
Initialize();
|
||||
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)
|
||||
{
|
||||
if (_updateFromTimeChange) return;
|
||||
if (_surpressTimeEvent) 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 minute = _minuteSelector?.SelectedValue ?? time.Minutes;
|
||||
var second = _secondSelector?.SelectedValue ?? time.Seconds;
|
||||
var ampm = _ampmSelector?.SelectedValue ?? (time.Hours >= 12 ? 1 : 0);
|
||||
if (_use12Clock)
|
||||
{
|
||||
hour = ampm switch
|
||||
{
|
||||
0 when hour == 12 => 0,
|
||||
1 when hour < 12 => hour + 12,
|
||||
_ => hour
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
ampm = hour switch
|
||||
@@ -261,11 +253,23 @@ public class TimePickerPresenter : TemplatedControl
|
||||
};
|
||||
SetIfChanged(_ampmSelector, ampm);
|
||||
}
|
||||
|
||||
var newTime = new TimeSpan(hour, minute, second);
|
||||
if (NeedsConfirmation)
|
||||
{
|
||||
TimeHolder = newTime;
|
||||
}
|
||||
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)
|
||||
@@ -275,21 +279,19 @@ public class TimePickerPresenter : TemplatedControl
|
||||
{
|
||||
var index = _use12Clock ? time.Value.Hours % 12 : time.Value.Hours;
|
||||
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
|
||||
{
|
||||
>= 12 => 1,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
SetIfChanged(_ampmSelector, ampm);
|
||||
if (_ampmSelector is not null)
|
||||
{
|
||||
_ampmSelector.IsEnabled = _use12Clock;
|
||||
}
|
||||
|
||||
SetIfChanged(_ampmSelector, ampm, true);
|
||||
if (_ampmSelector is not null) _ampmSelector.IsEnabled = _use12Clock;
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
@@ -326,12 +328,23 @@ public class TimePickerPresenter : TemplatedControl
|
||||
|
||||
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;
|
||||
panel.SelectionChanged -= OnPanelSelectionChanged;
|
||||
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()
|
||||
{
|
||||
Focus(NavigationMethod.Pointer);
|
||||
_startPresenter?.SetValue(TimePickerPresenter.TimeProperty, null);
|
||||
_endPresenter?.SetValue(TimePickerPresenter.TimeProperty, null);
|
||||
_startPresenter?.SyncTime(null);
|
||||
_endPresenter?.SyncTime(null);
|
||||
}
|
||||
|
||||
private void OnSelectionChanged(AvaloniaPropertyChangedEventArgs<TimeSpan?> args, bool start = true)
|
||||
{
|
||||
SyncTimeToText(args.NewValue.Value, start);
|
||||
_suppressTextPresenterEvent = true;
|
||||
TimePickerPresenter.TimeProperty.SetValue(args.NewValue.Value, start ? _startPresenter : _endPresenter);
|
||||
var presenter = start ? _startPresenter : _endPresenter;
|
||||
presenter?.SyncTime(args.NewValue.Value);
|
||||
_suppressTextPresenterEvent = false;
|
||||
}
|
||||
|
||||
@@ -150,8 +151,8 @@ public class TimeRangePicker : TimePickerBase, IClearControl
|
||||
Button.ClickEvent.AddHandler(OnButtonClick, _button);
|
||||
TimePickerPresenter.SelectedTimeChangedEvent.AddHandler(OnPresenterTimeChanged, _startPresenter, _endPresenter);
|
||||
|
||||
_startPresenter?.SetValue(TimePickerPresenter.TimeProperty, StartTime);
|
||||
_endPresenter?.SetValue(TimePickerPresenter.TimeProperty, EndTime);
|
||||
_startPresenter?.SyncTime(StartTime);
|
||||
_endPresenter?.SyncTime(EndTime);
|
||||
SyncTimeToText(StartTime);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,14 @@ public class DrawerCloseTestPopupControlVM : IDialogContext
|
||||
{
|
||||
RequestClose?.Invoke(this, Result);
|
||||
}
|
||||
|
||||
|
||||
#if NET8_0
|
||||
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;
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<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