csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
334 lines
13 KiB
334 lines
13 KiB
using System;
|
|
using System.Linq;
|
|
using Avalonia.Controls.Primitives;
|
|
using Avalonia.Controls.Templates;
|
|
using Avalonia.Data.Converters;
|
|
using Avalonia.Harfbuzz;
|
|
using Avalonia.Headless;
|
|
using Avalonia.Input;
|
|
using Avalonia.Platform;
|
|
using Avalonia.UnitTests;
|
|
using Moq;
|
|
using Xunit;
|
|
using System.Globalization;
|
|
|
|
namespace Avalonia.Controls.UnitTests
|
|
{
|
|
public class CalendarDatePickerTests : ScopedTestBase
|
|
{
|
|
private static bool CompareDates(DateTime first, DateTime second)
|
|
{
|
|
return first.Year == second.Year &&
|
|
first.Month == second.Month &&
|
|
first.Day == second.Day;
|
|
}
|
|
|
|
[Fact(Skip = "FIX ME ASAP")]
|
|
public void SelectedDateChanged_Should_Fire_When_SelectedDate_Set()
|
|
{
|
|
using (UnitTestApplication.Start(Services))
|
|
{
|
|
bool handled = false;
|
|
CalendarDatePicker datePicker = CreateControl();
|
|
datePicker.SelectedDateChanged += (s,e) =>
|
|
{
|
|
handled = true;
|
|
};
|
|
DateTime value = new DateTime(2000, 10, 10);
|
|
datePicker.SelectedDate = value;
|
|
Threading.Dispatcher.UIThread.RunJobs(null, TestContext.Current.CancellationToken);
|
|
Assert.True(handled);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Setting_Selected_Date_To_Blackout_Date_Should_Throw()
|
|
{
|
|
using (UnitTestApplication.Start(Services))
|
|
{
|
|
CalendarDatePicker datePicker = CreateControl();
|
|
Assert.NotNull(datePicker.BlackoutDates);
|
|
datePicker.BlackoutDates.AddDatesInPast();
|
|
|
|
DateTime goodValue = DateTime.Today.AddDays(1);
|
|
datePicker.SelectedDate = goodValue;
|
|
Assert.True(CompareDates(datePicker.SelectedDate.Value, goodValue));
|
|
|
|
DateTime badValue = DateTime.Today.AddDays(-1);
|
|
Assert.ThrowsAny<ArgumentOutOfRangeException>(
|
|
() => datePicker.SelectedDate = badValue);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Adding_Blackout_Dates_Containing_Selected_Date_Should_Throw()
|
|
{
|
|
using (UnitTestApplication.Start(Services))
|
|
{
|
|
CalendarDatePicker datePicker = CreateControl();
|
|
datePicker.SelectedDate = DateTime.Today.AddDays(5);
|
|
|
|
Assert.ThrowsAny<ArgumentOutOfRangeException>(
|
|
() => datePicker.BlackoutDates!.Add(new CalendarDateRange(DateTime.Today, DateTime.Today.AddDays(10))));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Setting_Date_Manually_With_CustomDateFormatString_Should_Be_Accepted()
|
|
{
|
|
CultureInfo.CurrentCulture = CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
|
|
using (UnitTestApplication.Start(Services))
|
|
{
|
|
CalendarDatePicker datePicker = CreateControl();
|
|
datePicker.SelectedDateFormat = CalendarDatePickerFormat.Custom;
|
|
datePicker.CustomDateFormatString = "dd.MM.yyyy";
|
|
|
|
var tb = GetTextBox(datePicker);
|
|
|
|
tb.Clear();
|
|
RaiseTextEvent(tb, "17.10.2024");
|
|
RaiseKeyEvent(tb, Key.Enter, KeyModifiers.None);
|
|
|
|
Assert.Equal("17.10.2024", datePicker.Text);
|
|
Assert.True(CompareDates(datePicker.SelectedDate!.Value, new DateTime(2024, 10, 17)));
|
|
|
|
tb.Clear();
|
|
RaiseTextEvent(tb, "12.10.2024");
|
|
RaiseKeyEvent(tb, Key.Enter, KeyModifiers.None);
|
|
|
|
Assert.Equal("12.10.2024", datePicker.Text);
|
|
Assert.True(CompareDates(datePicker.SelectedDate.Value, new DateTime(2024, 10, 12)));
|
|
}
|
|
}
|
|
|
|
private class CalendarDatePickerTextConverter : IValueConverter
|
|
{
|
|
// date to text
|
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
{
|
|
if (value is DateTime d)
|
|
return d.ToString("yyyy-MM-dd"); // always return a single format (for this test)
|
|
return AvaloniaProperty.UnsetValue;
|
|
}
|
|
|
|
// text to date
|
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
|
{
|
|
var str = value?.ToString();
|
|
if (str == null)
|
|
return AvaloniaProperty.UnsetValue;
|
|
// allow for a few different date formats
|
|
string[] formats = ["yyyy-MM-dd", "MM dd yyyy", "dd.MM.yyyy"];
|
|
if (DateTime.TryParseExact(str, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime dateValue))
|
|
{
|
|
return dateValue;
|
|
}
|
|
return AvaloniaProperty.UnsetValue;
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Setting_Date_Manually_Uses_Text_Converter()
|
|
{
|
|
CultureInfo.CurrentCulture = CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
|
|
using (UnitTestApplication.Start(Services))
|
|
{
|
|
CalendarDatePicker datePicker = CreateControl();
|
|
datePicker.SelectedDateFormat = CalendarDatePickerFormat.Custom;
|
|
datePicker.CustomDateFormatString = "dd.MM.yyyy";
|
|
datePicker.TextConverter = new CalendarDatePickerTextConverter();
|
|
var tb = GetTextBox(datePicker);
|
|
|
|
datePicker.SelectedDate = new DateTime(2024, 2, 13);
|
|
// DateTimeToString called async so need to let that complete before testing value
|
|
Threading.Dispatcher.UIThread.RunJobs(null, TestContext.Current.CancellationToken);
|
|
Assert.Equal("2024-02-13", datePicker.Text);
|
|
Assert.True(CompareDates(datePicker.SelectedDate!.Value, new DateTime(2024, 2, 13)));
|
|
|
|
// null input results in empty string for text
|
|
datePicker.SelectedDate = null;
|
|
|
|
Assert.Equal("", datePicker.Text);
|
|
Assert.Null(datePicker.SelectedDate);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Setting_Date_String_Manually_Can_Accept_Multiple_Formats()
|
|
{
|
|
CultureInfo.CurrentCulture = CultureInfo.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
|
|
using (UnitTestApplication.Start(Services))
|
|
{
|
|
CalendarDatePicker datePicker = CreateControl();
|
|
datePicker.SelectedDateFormat = CalendarDatePickerFormat.Custom;
|
|
datePicker.CustomDateFormatString = "dd.MM.yyyy";
|
|
datePicker.TextConverter = new CalendarDatePickerTextConverter();
|
|
var tb = GetTextBox(datePicker);
|
|
|
|
// parser can work with same format as CustomDateFormatString (but TextConverter must handle it)
|
|
tb.Clear();
|
|
RaiseTextEvent(tb, "17.10.2024");
|
|
RaiseKeyEvent(tb, Key.Enter, KeyModifiers.None);
|
|
Assert.Equal("2024-10-17", datePicker.Text);
|
|
Assert.True(CompareDates(datePicker.SelectedDate!.Value, new DateTime(2024, 10, 17)));
|
|
|
|
// can also handle parsing other formats that the user enters, too
|
|
tb.Clear();
|
|
RaiseTextEvent(tb, "2024-02-13");
|
|
RaiseKeyEvent(tb, Key.Enter, KeyModifiers.None);
|
|
|
|
Assert.Equal("2024-02-13", datePicker.Text);
|
|
Assert.True(CompareDates(datePicker.SelectedDate.Value, new DateTime(2024, 2, 13)));
|
|
|
|
tb.Clear();
|
|
RaiseTextEvent(tb, "04 22 2026");
|
|
RaiseKeyEvent(tb, Key.Enter, KeyModifiers.None);
|
|
|
|
Assert.Equal("2026-04-22", datePicker.Text);
|
|
Assert.True(CompareDates(datePicker.SelectedDate.Value, new DateTime(2026, 4, 22)));
|
|
|
|
// invalid input results in going back to last known (valid) date
|
|
tb.Clear();
|
|
RaiseTextEvent(tb, "Not A Valid Date");
|
|
RaiseKeyEvent(tb, Key.Enter, KeyModifiers.None);
|
|
|
|
Assert.Equal("2026-04-22", datePicker.Text);
|
|
Assert.True(CompareDates(datePicker.SelectedDate.Value, new DateTime(2026, 4, 22)));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Tab_Focus_Should_Move_Focus_To_TextBox()
|
|
{
|
|
using (UnitTestApplication.Start(FocusServices))
|
|
{
|
|
var datePicker = new CalendarDatePicker { Template = CreateTemplate() };
|
|
var root = new TestRoot(datePicker);
|
|
root.LayoutManager.ExecuteInitialLayoutPass();
|
|
|
|
datePicker.Focus(NavigationMethod.Tab);
|
|
|
|
Assert.Same(GetTextBox(datePicker), root.FocusManager.GetFocusedElement());
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void Programmatic_Focus_Should_Move_Focus_To_TextBox()
|
|
{
|
|
using (UnitTestApplication.Start(FocusServices))
|
|
{
|
|
var datePicker = new CalendarDatePicker { Template = CreateTemplate() };
|
|
var root = new TestRoot(datePicker);
|
|
root.LayoutManager.ExecuteInitialLayoutPass();
|
|
|
|
datePicker.Focus();
|
|
|
|
Assert.Same(GetTextBox(datePicker), root.FocusManager.GetFocusedElement());
|
|
}
|
|
}
|
|
|
|
private static TestServices FocusServices => TestServices.MockThreadingInterface.With(
|
|
fontManagerImpl: new HeadlessFontManagerStub(),
|
|
standardCursorFactory: Mock.Of<ICursorFactory>(),
|
|
textShaperImpl: new HarfBuzzTextShaper(),
|
|
renderInterface: new HeadlessPlatformRenderInterface(),
|
|
keyboardDevice: () => new KeyboardDevice(),
|
|
keyboardNavigation: () => new KeyboardNavigationHandler(),
|
|
inputManager: new InputManager());
|
|
|
|
private static TestServices Services => TestServices.MockThreadingInterface.With(
|
|
standardCursorFactory: Mock.Of<ICursorFactory>());
|
|
|
|
private static CalendarDatePicker CreateControl()
|
|
{
|
|
var datePicker =
|
|
new CalendarDatePicker
|
|
{
|
|
Template = CreateTemplate()
|
|
};
|
|
|
|
datePicker.ApplyTemplate();
|
|
return datePicker;
|
|
}
|
|
|
|
private static IControlTemplate CreateTemplate()
|
|
{
|
|
return new FuncControlTemplate<CalendarDatePicker>((control, scope) =>
|
|
{
|
|
var textBox =
|
|
new TextBox
|
|
{
|
|
Name = "PART_TextBox"
|
|
}.RegisterInNameScope(scope);
|
|
var button =
|
|
new Button
|
|
{
|
|
Name = "PART_Button"
|
|
}.RegisterInNameScope(scope);
|
|
var calendar =
|
|
new Calendar
|
|
{
|
|
Name = "PART_Calendar",
|
|
[!Calendar.SelectedDateProperty] = control[!CalendarDatePicker.SelectedDateProperty],
|
|
[!Calendar.DisplayDateProperty] = control[!CalendarDatePicker.DisplayDateProperty],
|
|
[!Calendar.DisplayDateStartProperty] = control[!CalendarDatePicker.DisplayDateStartProperty],
|
|
[!Calendar.DisplayDateEndProperty] = control[!CalendarDatePicker.DisplayDateEndProperty]
|
|
}.RegisterInNameScope(scope);
|
|
var popup =
|
|
new Popup
|
|
{
|
|
Name = "PART_Popup"
|
|
}.RegisterInNameScope(scope);
|
|
|
|
var panel = new Panel();
|
|
panel.Children.Add(textBox);
|
|
panel.Children.Add(button);
|
|
panel.Children.Add(popup);
|
|
panel.Children.Add(calendar);
|
|
|
|
return panel;
|
|
});
|
|
|
|
}
|
|
|
|
private TextBox GetTextBox(CalendarDatePicker control)
|
|
{
|
|
return control.GetTemplateDescendants()
|
|
.OfType<TextBox>()
|
|
.First();
|
|
}
|
|
|
|
private static void RaiseKeyEvent(TextBox textBox, Key key, KeyModifiers inputModifiers)
|
|
{
|
|
textBox.RaiseEvent(new KeyEventArgs
|
|
{
|
|
RoutedEvent = InputElement.KeyDownEvent,
|
|
KeyModifiers = inputModifiers,
|
|
Key = key
|
|
});
|
|
}
|
|
|
|
private static void RaiseTextEvent(TextBox textBox, string text)
|
|
{
|
|
textBox.RaiseEvent(new TextInputEventArgs
|
|
{
|
|
RoutedEvent = InputElement.TextInputEvent,
|
|
Text = text
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void PlaceholderForeground_Can_Be_Set()
|
|
{
|
|
using (UnitTestApplication.Start(Services))
|
|
{
|
|
var control = CreateControl();
|
|
control.PlaceholderText = "Select date";
|
|
control.PlaceholderForeground = Media.Brushes.Purple;
|
|
|
|
Assert.Equal(Media.Brushes.Purple, control.PlaceholderForeground);
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|