DatePickerTests.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. using System;
  2. using System.Linq;
  3. using System.Reactive.Subjects;
  4. using Avalonia.Controls.Primitives;
  5. using Avalonia.Controls.Shapes;
  6. using Avalonia.Controls.Templates;
  7. using Avalonia.Data;
  8. using Avalonia.Headless;
  9. using Avalonia.Platform;
  10. using Avalonia.Threading;
  11. using Avalonia.UnitTests;
  12. using Avalonia.VisualTree;
  13. using Moq;
  14. using Xunit;
  15. namespace Avalonia.Controls.UnitTests
  16. {
  17. public class DatePickerTests : ScopedTestBase
  18. {
  19. [Fact]
  20. public void SelectedDateChanged_Should_Fire_When_SelectedDate_Set()
  21. {
  22. using (UnitTestApplication.Start(Services))
  23. {
  24. bool handled = false;
  25. DatePicker datePicker = new DatePicker();
  26. datePicker.SelectedDateChanged += (s, e) =>
  27. {
  28. handled = true;
  29. };
  30. DateTimeOffset value = new DateTimeOffset(2000, 10, 10, 0, 0, 0, TimeSpan.Zero);
  31. datePicker.SelectedDate = value;
  32. Threading.Dispatcher.UIThread.RunJobs();
  33. Assert.True(handled);
  34. }
  35. }
  36. [Fact]
  37. public void DayVisible_False_Should_Hide_Day()
  38. {
  39. using (UnitTestApplication.Start(Services))
  40. {
  41. DatePicker datePicker = new DatePicker
  42. {
  43. Template = CreateTemplate(),
  44. DayVisible = false
  45. };
  46. datePicker.ApplyTemplate();
  47. Threading.Dispatcher.UIThread.RunJobs();
  48. var desc = datePicker.GetVisualDescendants();
  49. Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
  50. TextBlock dayText = null;
  51. Grid container = null;
  52. Assert.True(desc.ElementAt(1) is Button);
  53. container = (desc.ElementAt(1) as Button).Content as Grid;
  54. Assert.True(container != null);
  55. for(int i = 0; i < container.Children.Count; i++)
  56. {
  57. if(container.Children[i] is TextBlock tb && tb.Name == "PART_DayTextBlock")
  58. {
  59. dayText = tb;
  60. break;
  61. }
  62. }
  63. Assert.True(dayText != null);
  64. Assert.True(!dayText.IsVisible);
  65. Assert.True(container.ColumnDefinitions.Count == 3);
  66. }
  67. }
  68. [Fact]
  69. public void MonthVisible_False_Should_Hide_Month()
  70. {
  71. using (UnitTestApplication.Start(Services))
  72. {
  73. DatePicker datePicker = new DatePicker
  74. {
  75. Template = CreateTemplate(),
  76. MonthVisible = false
  77. };
  78. datePicker.ApplyTemplate();
  79. Threading.Dispatcher.UIThread.RunJobs();
  80. var desc = datePicker.GetVisualDescendants();
  81. Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
  82. TextBlock monthText = null;
  83. Grid container = null;
  84. Assert.True(desc.ElementAt(1) is Button);
  85. container = (desc.ElementAt(1) as Button).Content as Grid;
  86. Assert.True(container != null);
  87. for (int i = 0; i < container.Children.Count; i++)
  88. {
  89. if (container.Children[i] is TextBlock tb && tb.Name == "PART_MonthTextBlock")
  90. {
  91. monthText = tb;
  92. break;
  93. }
  94. }
  95. Assert.True(monthText != null);
  96. Assert.True(!monthText.IsVisible);
  97. Assert.True(container.ColumnDefinitions.Count == 3);
  98. }
  99. }
  100. [Fact]
  101. public void YearVisible_False_Should_Hide_Year()
  102. {
  103. using (UnitTestApplication.Start(Services))
  104. {
  105. DatePicker datePicker = new DatePicker
  106. {
  107. Template = CreateTemplate(),
  108. YearVisible = false
  109. };
  110. datePicker.ApplyTemplate();
  111. Threading.Dispatcher.UIThread.RunJobs();
  112. var desc = datePicker.GetVisualDescendants();
  113. Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
  114. TextBlock yearText = null;
  115. Grid container = null;
  116. Assert.True(desc.ElementAt(1) is Button);
  117. container = (desc.ElementAt(1) as Button).Content as Grid;
  118. Assert.True(container != null);
  119. for (int i = 0; i < container.Children.Count; i++)
  120. {
  121. if (container.Children[i] is TextBlock tb && tb.Name == "PART_YearTextBlock")
  122. {
  123. yearText = tb;
  124. break;
  125. }
  126. }
  127. Assert.True(yearText != null);
  128. Assert.True(!yearText.IsVisible);
  129. Assert.True(container.ColumnDefinitions.Count == 3);
  130. }
  131. }
  132. [Fact]
  133. public void SelectedDate_null_Should_Use_Placeholders()
  134. {
  135. using (UnitTestApplication.Start(Services))
  136. {
  137. DatePicker datePicker = new DatePicker
  138. {
  139. Template = CreateTemplate(),
  140. YearVisible = false
  141. };
  142. datePicker.ApplyTemplate();
  143. Threading.Dispatcher.UIThread.RunJobs();
  144. var desc = datePicker.GetVisualDescendants();
  145. Assert.True(desc.Count() > 1);//Should be layoutroot grid & button
  146. TextBlock yearText = null;
  147. TextBlock monthText = null;
  148. TextBlock dayText = null;
  149. Grid container = null;
  150. Assert.True(desc.ElementAt(1) is Button);
  151. container = (desc.ElementAt(1) as Button).Content as Grid;
  152. Assert.True(container != null);
  153. for (int i = 0; i < container.Children.Count; i++)
  154. {
  155. if (container.Children[i] is TextBlock tb && tb.Name == "PART_YearTextBlock")
  156. {
  157. yearText = tb;
  158. }
  159. else if (container.Children[i] is TextBlock tb1 && tb1.Name == "PART_MonthTextBlock")
  160. {
  161. monthText = tb1;
  162. }
  163. else if (container.Children[i] is TextBlock tb2 && tb2.Name == "PART_DayTextBlock")
  164. {
  165. dayText = tb2;
  166. }
  167. }
  168. DateTimeOffset value = new DateTimeOffset(2000, 10, 10, 0, 0, 0, TimeSpan.Zero);
  169. datePicker.SelectedDate = value;
  170. Assert.NotNull(dayText.Text);
  171. Assert.NotNull(monthText.Text);
  172. Assert.NotNull(yearText.Text);
  173. Assert.False(datePicker.Classes.Contains(":hasnodate"));
  174. datePicker.SelectedDate = null;
  175. Assert.Null(dayText.Text);
  176. Assert.Null(monthText.Text);
  177. Assert.Null(yearText.Text);
  178. Assert.True(datePicker.Classes.Contains(":hasnodate"));
  179. }
  180. }
  181. [Fact]
  182. public void SelectedDate_EnableDataValidation()
  183. {
  184. var handled = false;
  185. var datePicker = new DatePicker();
  186. datePicker.SelectedDateChanged += (s, e) =>
  187. {
  188. var minDateTime = new DateTimeOffset(2000, 1, 1, 0, 0, 0, TimeSpan.Zero);
  189. var maxDateTime = new DateTimeOffset(2010, 1, 1, 0, 0, 0, TimeSpan.Zero);
  190. if (e.NewDate < minDateTime)
  191. throw new DataValidationException($"dateTime is less than {minDateTime}");
  192. if (e.NewDate > maxDateTime)
  193. throw new DataValidationException($"dateTime is over {maxDateTime}");
  194. handled = true;
  195. };
  196. // dateTime is less than
  197. Assert.Throws<DataValidationException>(() => datePicker.SelectedDate = new DateTimeOffset(1999, 1, 1, 0, 0, 0, TimeSpan.Zero));
  198. // dateTime is over
  199. Assert.Throws<DataValidationException>(() => datePicker.SelectedDate = new DateTimeOffset(2021, 1, 1, 0, 0, 0, TimeSpan.Zero));
  200. var exception = new DataValidationException("failed validation");
  201. var observable =
  202. new BehaviorSubject<BindingNotification>(new BindingNotification(exception,
  203. BindingErrorType.DataValidationError));
  204. datePicker.Bind(DatePicker.SelectedDateProperty, observable);
  205. Assert.True(DataValidationErrors.GetHasErrors(datePicker));
  206. Dispatcher.UIThread.RunJobs();
  207. datePicker.SelectedDate = new DateTimeOffset(2005, 5, 10, 11, 12, 13, TimeSpan.Zero);
  208. Assert.True(handled);
  209. }
  210. [Theory]
  211. [InlineData("PART_DaySelector")]
  212. [InlineData("PART_MonthSelector")]
  213. [InlineData("PART_YearSelector")]
  214. public void Selector_ScrollUp_Should_Work(string selectorName)
  215. => TestSelectorScrolling(selectorName, panel => panel.ScrollUp());
  216. [Theory]
  217. [InlineData("PART_DaySelector")]
  218. [InlineData("PART_MonthSelector")]
  219. [InlineData("PART_YearSelector")]
  220. public void Selector_ScrollDown_Should_Work(string selectorName)
  221. => TestSelectorScrolling(selectorName, panel => panel.ScrollDown());
  222. private static void TestSelectorScrolling(string selectorName, Action<DateTimePickerPanel> scroll)
  223. {
  224. using var app = UnitTestApplication.Start(Services);
  225. var presenter = new DatePickerPresenter { Template = CreatePickerTemplate() };
  226. presenter.ApplyTemplate();
  227. presenter.Measure(new Size(1000, 1000));
  228. var panel = presenter
  229. .GetVisualDescendants()
  230. .OfType<DateTimePickerPanel>()
  231. .FirstOrDefault(panel => panel.Name == selectorName);
  232. Assert.NotNull(panel);
  233. var previousOffset = panel.Offset;
  234. scroll(panel);
  235. Assert.NotEqual(previousOffset, panel.Offset);
  236. }
  237. private static TestServices Services => TestServices.MockThreadingInterface.With(
  238. fontManagerImpl: new HeadlessFontManagerStub(),
  239. standardCursorFactory: Mock.Of<ICursorFactory>(),
  240. textShaperImpl: new HeadlessTextShaperStub(),
  241. renderInterface: new HeadlessPlatformRenderInterface());
  242. private static IControlTemplate CreateTemplate()
  243. {
  244. return new FuncControlTemplate((control, scope) =>
  245. {
  246. var layoutRoot = new Grid
  247. {
  248. Name = "LayoutRoot"
  249. }.RegisterInNameScope(scope);
  250. //Skip contentpresenter
  251. var flyoutButton = new Button
  252. {
  253. Name = "PART_FlyoutButton"
  254. }.RegisterInNameScope(scope);
  255. var contentGrid = new Grid
  256. {
  257. Name = "PART_ButtonContentGrid"
  258. }.RegisterInNameScope(scope);
  259. var dayText = new TextBlock
  260. {
  261. Name = "PART_DayTextBlock"
  262. }.RegisterInNameScope(scope);
  263. var monthText = new TextBlock
  264. {
  265. Name = "PART_MonthTextBlock"
  266. }.RegisterInNameScope(scope);
  267. var yearText = new TextBlock
  268. {
  269. Name = "PART_YearTextBlock"
  270. }.RegisterInNameScope(scope);
  271. var firstSpacer = new Rectangle
  272. {
  273. Name = "PART_FirstSpacer"
  274. }.RegisterInNameScope(scope);
  275. var secondSpacer = new Rectangle
  276. {
  277. Name = "PART_SecondSpacer"
  278. }.RegisterInNameScope(scope);
  279. var thirdSpacer = new Rectangle
  280. {
  281. Name = "PART_ThirdSpacer"
  282. }.RegisterInNameScope(scope);
  283. contentGrid.Children.AddRange(new Control[] { dayText, monthText, yearText, firstSpacer, secondSpacer, thirdSpacer });
  284. flyoutButton.Content = contentGrid;
  285. layoutRoot.Children.Add(flyoutButton);
  286. return layoutRoot;
  287. });
  288. }
  289. private static IControlTemplate CreatePickerTemplate()
  290. {
  291. return new FuncControlTemplate((_, scope) =>
  292. {
  293. var dayHost = new Panel
  294. {
  295. Name = "PART_DayHost"
  296. }.RegisterInNameScope(scope);
  297. var daySelector = new DateTimePickerPanel
  298. {
  299. Name = "PART_DaySelector",
  300. PanelType = DateTimePickerPanelType.Day,
  301. ShouldLoop = true
  302. }.RegisterInNameScope(scope);
  303. var monthHost = new Panel
  304. {
  305. Name = "PART_MonthHost"
  306. }.RegisterInNameScope(scope);
  307. var monthSelector = new DateTimePickerPanel
  308. {
  309. Name = "PART_MonthSelector",
  310. PanelType = DateTimePickerPanelType.Month,
  311. ShouldLoop = true
  312. }.RegisterInNameScope(scope);
  313. var yearHost = new Panel
  314. {
  315. Name = "PART_YearHost"
  316. }.RegisterInNameScope(scope);
  317. var yearSelector = new DateTimePickerPanel
  318. {
  319. Name = "PART_YearSelector",
  320. PanelType = DateTimePickerPanelType.Year,
  321. ShouldLoop = true
  322. }.RegisterInNameScope(scope);
  323. var acceptButton = new Button
  324. {
  325. Name = "PART_AcceptButton"
  326. }.RegisterInNameScope(scope);
  327. var pickerContainer = new Grid
  328. {
  329. Name = "PART_PickerContainer"
  330. }.RegisterInNameScope(scope);
  331. var firstSpacer = new Rectangle
  332. {
  333. Name = "PART_FirstSpacer"
  334. }.RegisterInNameScope(scope);
  335. var secondSpacer = new Rectangle
  336. {
  337. Name = "PART_SecondSpacer"
  338. }.RegisterInNameScope(scope);
  339. var contentPanel = new Panel();
  340. contentPanel.Children.AddRange([
  341. dayHost, daySelector, monthHost, monthSelector, yearHost, yearSelector,
  342. acceptButton, pickerContainer, firstSpacer, secondSpacer
  343. ]);
  344. return contentPanel;
  345. });
  346. }
  347. }
  348. }