Browse Source

Fix DateTimePickerPanel scrolling exception (#18584)

* Add failing tests for DateTimePickerPanel

* Fix DateTimePickerPanel scrolling exception
release/11.3.0-beta2
Julien Lebosquain 10 months ago
parent
commit
dceef40edc
  1. 2
      src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs
  2. 104
      tests/Avalonia.Controls.UnitTests/DatePickerTests.cs
  3. 39
      tests/Avalonia.Controls.UnitTests/TimePickerTests.cs

2
src/Avalonia.Controls/DateTimePickers/DateTimePickerPanel.cs

@ -234,7 +234,7 @@ namespace Avalonia.Controls.Primitives
else
break;
}
children.MoveRange(0, numCountsToMove, children.Count);
children.MoveRange(0, numCountsToMove, children.Count - 1);
var scrollHeight = _extent.Height - Viewport.Height;
if (ShouldLoop && value.Y >= scrollHeight - _extentOne)

104
tests/Avalonia.Controls.UnitTests/DatePickerTests.cs

@ -1,6 +1,7 @@
using System;
using System.Linq;
using System.Reactive.Subjects;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Data;
@ -244,6 +245,40 @@ namespace Avalonia.Controls.UnitTests
Assert.True(handled);
}
[Theory]
[InlineData("PART_DaySelector")]
[InlineData("PART_MonthSelector")]
[InlineData("PART_YearSelector")]
public void Selector_ScrollUp_Should_Work(string selectorName)
=> TestSelectorScrolling(selectorName, panel => panel.ScrollUp());
[Theory]
[InlineData("PART_DaySelector")]
[InlineData("PART_MonthSelector")]
[InlineData("PART_YearSelector")]
public void Selector_ScrollDown_Should_Work(string selectorName)
=> TestSelectorScrolling(selectorName, panel => panel.ScrollDown());
private static void TestSelectorScrolling(string selectorName, Action<DateTimePickerPanel> scroll)
{
using var app = UnitTestApplication.Start(Services);
var presenter = new DatePickerPresenter { Template = CreatePickerTemplate() };
presenter.ApplyTemplate();
presenter.Measure(new Size(1000, 1000));
var panel = presenter
.GetVisualDescendants()
.OfType<DateTimePickerPanel>()
.FirstOrDefault(panel => panel.Name == selectorName);
Assert.NotNull(panel);
var previousOffset = panel.Offset;
scroll(panel);
Assert.NotEqual(previousOffset, panel.Offset);
}
private static TestServices Services => TestServices.MockThreadingInterface.With(
fontManagerImpl: new HeadlessFontManagerStub(),
standardCursorFactory: Mock.Of<ICursorFactory>(),
@ -298,5 +333,74 @@ namespace Avalonia.Controls.UnitTests
return layoutRoot;
});
}
private static IControlTemplate CreatePickerTemplate()
{
return new FuncControlTemplate((_, scope) =>
{
var dayHost = new Panel
{
Name = "PART_DayHost"
}.RegisterInNameScope(scope);
var daySelector = new DateTimePickerPanel
{
Name = "PART_DaySelector",
PanelType = DateTimePickerPanelType.Day,
ShouldLoop = true
}.RegisterInNameScope(scope);
var monthHost = new Panel
{
Name = "PART_MonthHost"
}.RegisterInNameScope(scope);
var monthSelector = new DateTimePickerPanel
{
Name = "PART_MonthSelector",
PanelType = DateTimePickerPanelType.Month,
ShouldLoop = true
}.RegisterInNameScope(scope);
var yearHost = new Panel
{
Name = "PART_YearHost"
}.RegisterInNameScope(scope);
var yearSelector = new DateTimePickerPanel
{
Name = "PART_YearSelector",
PanelType = DateTimePickerPanelType.Year,
ShouldLoop = true
}.RegisterInNameScope(scope);
var acceptButton = new Button
{
Name = "PART_AcceptButton"
}.RegisterInNameScope(scope);
var pickerContainer = new Grid
{
Name = "PART_PickerContainer"
}.RegisterInNameScope(scope);
var firstSpacer = new Rectangle
{
Name = "PART_FirstSpacer"
}.RegisterInNameScope(scope);
var secondSpacer = new Rectangle
{
Name = "PART_SecondSpacer"
}.RegisterInNameScope(scope);
var contentPanel = new Panel();
contentPanel.Children.AddRange([
dayHost, daySelector, monthHost, monthSelector, yearHost, yearSelector,
acceptButton, pickerContainer, firstSpacer, secondSpacer
]);
return contentPanel;
});
}
}
}

39
tests/Avalonia.Controls.UnitTests/TimePickerTests.cs

@ -275,6 +275,40 @@ namespace Avalonia.Controls.UnitTests
}
}
[Theory]
[InlineData("PART_HourSelector")]
[InlineData("PART_MinuteSelector")]
[InlineData("PART_SecondSelector")]
public void Selector_ScrollUp_Should_Work(string selectorName)
=> TestSelectorScrolling(selectorName, panel => panel.ScrollUp());
[Theory]
[InlineData("PART_HourSelector")]
[InlineData("PART_MinuteSelector")]
[InlineData("PART_SecondSelector")]
public void Selector_ScrollDown_Should_Work(string selectorName)
=> TestSelectorScrolling(selectorName, panel => panel.ScrollDown());
private static void TestSelectorScrolling(string selectorName, Action<DateTimePickerPanel> scroll)
{
using var app = UnitTestApplication.Start(Services);
var presenter = new TimePickerPresenter { Template = CreatePickerTemplate() };
presenter.ApplyTemplate();
presenter.Measure(new Size(1000, 1000));
var panel = presenter
.GetVisualDescendants()
.OfType<DateTimePickerPanel>()
.FirstOrDefault(panel => panel.Name == selectorName);
Assert.NotNull(panel);
var previousOffset = panel.Offset;
scroll(panel);
Assert.NotEqual(previousOffset, panel.Offset);
}
private static TestServices Services => TestServices.MockThreadingInterface.With(
fontManagerImpl: new HeadlessFontManagerStub(),
standardCursorFactory: Mock.Of<ICursorFactory>(),
@ -398,12 +432,14 @@ namespace Avalonia.Controls.UnitTests
{
Name = "PART_HourSelector",
PanelType = DateTimePickerPanelType.Hour,
ShouldLoop = true
}.RegisterInNameScope(scope);
var minuteSelector = new DateTimePickerPanel
{
Name = "PART_MinuteSelector",
PanelType = DateTimePickerPanelType.Minute,
ShouldLoop = true
}.RegisterInNameScope(scope);
var secondHost = new Panel
@ -415,6 +451,7 @@ namespace Avalonia.Controls.UnitTests
{
Name = "PART_SecondSelector",
PanelType = DateTimePickerPanelType.Second,
ShouldLoop = true
}.RegisterInNameScope(scope);
var periodHost = new Panel
@ -443,7 +480,7 @@ namespace Avalonia.Controls.UnitTests
Name = "PART_ThirdSpacer"
}.RegisterInNameScope(scope);
var contentPanel = new StackPanel();
var contentPanel = new Panel();
contentPanel.Children.AddRange(new Control[] { acceptButton, hourSelector, minuteSelector, secondHost, secondSelector, periodHost, periodSelector, pickerContainer, secondSpacer, thirdSpacer });
return contentPanel;
});

Loading…
Cancel
Save