Browse Source

Fix DateTimePickerPanel scrolling exception (#18584)

* Add failing tests for DateTimePickerPanel

* Fix DateTimePickerPanel scrolling exception
release/11.3.0-beta2
Julien Lebosquain 2 years 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 else
break; break;
} }
children.MoveRange(0, numCountsToMove, children.Count); children.MoveRange(0, numCountsToMove, children.Count - 1);
var scrollHeight = _extent.Height - Viewport.Height; var scrollHeight = _extent.Height - Viewport.Height;
if (ShouldLoop && value.Y >= scrollHeight - _extentOne) if (ShouldLoop && value.Y >= scrollHeight - _extentOne)

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

@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes; using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data; using Avalonia.Data;
@ -244,6 +245,40 @@ namespace Avalonia.Controls.UnitTests
Assert.True(handled); 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( private static TestServices Services => TestServices.MockThreadingInterface.With(
fontManagerImpl: new HeadlessFontManagerStub(), fontManagerImpl: new HeadlessFontManagerStub(),
standardCursorFactory: Mock.Of<ICursorFactory>(), standardCursorFactory: Mock.Of<ICursorFactory>(),
@ -298,5 +333,74 @@ namespace Avalonia.Controls.UnitTests
return layoutRoot; 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( private static TestServices Services => TestServices.MockThreadingInterface.With(
fontManagerImpl: new HeadlessFontManagerStub(), fontManagerImpl: new HeadlessFontManagerStub(),
standardCursorFactory: Mock.Of<ICursorFactory>(), standardCursorFactory: Mock.Of<ICursorFactory>(),
@ -398,12 +432,14 @@ namespace Avalonia.Controls.UnitTests
{ {
Name = "PART_HourSelector", Name = "PART_HourSelector",
PanelType = DateTimePickerPanelType.Hour, PanelType = DateTimePickerPanelType.Hour,
ShouldLoop = true
}.RegisterInNameScope(scope); }.RegisterInNameScope(scope);
var minuteSelector = new DateTimePickerPanel var minuteSelector = new DateTimePickerPanel
{ {
Name = "PART_MinuteSelector", Name = "PART_MinuteSelector",
PanelType = DateTimePickerPanelType.Minute, PanelType = DateTimePickerPanelType.Minute,
ShouldLoop = true
}.RegisterInNameScope(scope); }.RegisterInNameScope(scope);
var secondHost = new Panel var secondHost = new Panel
@ -415,6 +451,7 @@ namespace Avalonia.Controls.UnitTests
{ {
Name = "PART_SecondSelector", Name = "PART_SecondSelector",
PanelType = DateTimePickerPanelType.Second, PanelType = DateTimePickerPanelType.Second,
ShouldLoop = true
}.RegisterInNameScope(scope); }.RegisterInNameScope(scope);
var periodHost = new Panel var periodHost = new Panel
@ -443,7 +480,7 @@ namespace Avalonia.Controls.UnitTests
Name = "PART_ThirdSpacer" Name = "PART_ThirdSpacer"
}.RegisterInNameScope(scope); }.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 }); contentPanel.Children.AddRange(new Control[] { acceptButton, hourSelector, minuteSelector, secondHost, secondSelector, periodHost, periodSelector, pickerContainer, secondSpacer, thirdSpacer });
return contentPanel; return contentPanel;
}); });

Loading…
Cancel
Save