ListBoxTests.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. // Copyright (c) The Avalonia Project. All rights reserved.
  2. // Licensed under the MIT license. See licence.md file in the project root for full license information.
  3. using System;
  4. using System.Collections.ObjectModel;
  5. using System.Linq;
  6. using Avalonia.Collections;
  7. using Avalonia.Controls.Presenters;
  8. using Avalonia.Controls.Templates;
  9. using Avalonia.Data;
  10. using Avalonia.LogicalTree;
  11. using Avalonia.Styling;
  12. using Avalonia.UnitTests;
  13. using Avalonia.VisualTree;
  14. using Xunit;
  15. namespace Avalonia.Controls.UnitTests
  16. {
  17. public class ListBoxTests
  18. {
  19. [Fact]
  20. public void Should_Use_ItemTemplate_To_Create_Item_Content()
  21. {
  22. var target = new ListBox
  23. {
  24. Template = ListBoxTemplate(),
  25. Items = new[] { "Foo" },
  26. ItemTemplate = new FuncDataTemplate<string>(_ => new Canvas()),
  27. };
  28. Prepare(target);
  29. var container = (ListBoxItem)target.Presenter.Panel.Children[0];
  30. Assert.IsType<Canvas>(container.Presenter.Child);
  31. }
  32. [Fact]
  33. public void ListBox_Should_Find_ItemsPresenter_In_ScrollViewer()
  34. {
  35. var target = new ListBox
  36. {
  37. Template = ListBoxTemplate(),
  38. };
  39. Prepare(target);
  40. Assert.IsType<ItemsPresenter>(target.Presenter);
  41. }
  42. [Fact]
  43. public void ListBoxItem_Containers_Should_Be_Generated()
  44. {
  45. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  46. {
  47. var items = new[] { "Foo", "Bar", "Baz " };
  48. var target = new ListBox
  49. {
  50. Template = ListBoxTemplate(),
  51. Items = items,
  52. };
  53. Prepare(target);
  54. var text = target.Presenter.Panel.Children
  55. .OfType<ListBoxItem>()
  56. .Select(x => x.Presenter.Child)
  57. .OfType<TextBlock>()
  58. .Select(x => x.Text)
  59. .ToList();
  60. Assert.Equal(items, text);
  61. }
  62. }
  63. [Fact]
  64. public void LogicalChildren_Should_Be_Set_For_DataTemplate_Generated_Items()
  65. {
  66. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  67. {
  68. var target = new ListBox
  69. {
  70. Template = ListBoxTemplate(),
  71. Items = new[] { "Foo", "Bar", "Baz " },
  72. };
  73. Prepare(target);
  74. Assert.Equal(3, target.GetLogicalChildren().Count());
  75. foreach (var child in target.GetLogicalChildren())
  76. {
  77. Assert.IsType<ListBoxItem>(child);
  78. }
  79. }
  80. }
  81. [Fact]
  82. public void DataContexts_Should_Be_Correctly_Set()
  83. {
  84. using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
  85. {
  86. var items = new object[]
  87. {
  88. "Foo",
  89. new Item("Bar"),
  90. new TextBlock { Text = "Baz" },
  91. new ListBoxItem { Content = "Qux" },
  92. };
  93. var target = new ListBox
  94. {
  95. Template = ListBoxTemplate(),
  96. DataContext = "Base",
  97. DataTemplates =
  98. {
  99. new FuncDataTemplate<Item>(x => new Button { Content = x })
  100. },
  101. Items = items,
  102. };
  103. Prepare(target);
  104. var dataContexts = target.Presenter.Panel.Children
  105. .Cast<Control>()
  106. .Select(x => x.DataContext)
  107. .ToList();
  108. Assert.Equal(
  109. new object[] { items[0], items[1], "Base", "Base" },
  110. dataContexts);
  111. }
  112. }
  113. [Fact]
  114. public void Selection_Should_Be_Cleared_On_Recycled_Items()
  115. {
  116. var target = new ListBox
  117. {
  118. Template = ListBoxTemplate(),
  119. Items = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
  120. ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Height = 10 }),
  121. SelectedIndex = 0,
  122. };
  123. Prepare(target);
  124. // Make sure we're virtualized and first item is selected.
  125. Assert.Equal(10, target.Presenter.Panel.Children.Count);
  126. Assert.True(((ListBoxItem)target.Presenter.Panel.Children[0]).IsSelected);
  127. // Scroll down a page.
  128. target.Scroll.Offset = new Vector(0, 10);
  129. // Make sure recycled item isn't now selected.
  130. Assert.False(((ListBoxItem)target.Presenter.Panel.Children[0]).IsSelected);
  131. }
  132. [Fact]
  133. public void ScrollViewer_Should_Have_Correct_Extent_And_Viewport()
  134. {
  135. var target = new ListBox
  136. {
  137. Template = ListBoxTemplate(),
  138. Items = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList(),
  139. ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Width = 20, Height = 10 }),
  140. SelectedIndex = 0,
  141. };
  142. Prepare(target);
  143. Assert.Equal(new Size(20, 20), target.Scroll.Extent);
  144. Assert.Equal(new Size(100, 10), target.Scroll.Viewport);
  145. }
  146. [Fact]
  147. public void Containers_Correct_After_Clear_Add_Remove()
  148. {
  149. // Issue #1936
  150. var items = new AvaloniaList<string>(Enumerable.Range(0, 11).Select(x => $"Item {x}"));
  151. var target = new ListBox
  152. {
  153. Template = ListBoxTemplate(),
  154. Items = items,
  155. ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Width = 20, Height = 10 }),
  156. SelectedIndex = 0,
  157. };
  158. Prepare(target);
  159. items.Clear();
  160. items.AddRange(Enumerable.Range(0, 11).Select(x => $"Item {x}"));
  161. items.Remove("Item 2");
  162. Assert.Equal(
  163. items,
  164. target.Presenter.Panel.Children.Cast<ListBoxItem>().Select(x => (string)x.Content));
  165. }
  166. [Fact]
  167. public void ListBox_After_Scroll_IndexOutOfRangeException_Shouldnt_Be_Thrown()
  168. {
  169. var items = Enumerable.Range(0, 11).Select(x => $"{x}").ToArray();
  170. var target = new ListBox
  171. {
  172. Template = ListBoxTemplate(),
  173. Items = items,
  174. ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Height = 11 })
  175. };
  176. Prepare(target);
  177. var panel = target.Presenter.Panel as IVirtualizingPanel;
  178. var listBoxItems = panel.Children.OfType<ListBoxItem>();
  179. //virtualization should have created exactly 10 items
  180. Assert.Equal(10, listBoxItems.Count());
  181. Assert.Equal("0", listBoxItems.First().DataContext);
  182. Assert.Equal("9", listBoxItems.Last().DataContext);
  183. //instead pixeloffset > 0 there could be pretty complex sequence for repro
  184. //it involves add/remove/scroll to end multiple actions
  185. //which i can't find so far :(, but this is the simplest way to add it to unit test
  186. panel.PixelOffset = 1;
  187. //here scroll to end -> IndexOutOfRangeException is thrown
  188. target.Scroll.Offset = new Vector(0, 2);
  189. Assert.True(true);
  190. }
  191. private FuncControlTemplate ListBoxTemplate()
  192. {
  193. return new FuncControlTemplate<ListBox>(parent =>
  194. new ScrollViewer
  195. {
  196. Name = "PART_ScrollViewer",
  197. Template = ScrollViewerTemplate(),
  198. Content = new ItemsPresenter
  199. {
  200. Name = "PART_ItemsPresenter",
  201. [~ItemsPresenter.ItemsProperty] = parent.GetObservable(ItemsControl.ItemsProperty).ToBinding(),
  202. [~ItemsPresenter.ItemsPanelProperty] = parent.GetObservable(ItemsControl.ItemsPanelProperty).ToBinding(),
  203. [~ItemsPresenter.VirtualizationModeProperty] = parent.GetObservable(ListBox.VirtualizationModeProperty).ToBinding(),
  204. }
  205. });
  206. }
  207. private FuncControlTemplate ListBoxItemTemplate()
  208. {
  209. return new FuncControlTemplate<ListBoxItem>(parent =>
  210. new ContentPresenter
  211. {
  212. Name = "PART_ContentPresenter",
  213. [!ContentPresenter.ContentProperty] = parent[!ListBoxItem.ContentProperty],
  214. [!ContentPresenter.ContentTemplateProperty] = parent[!ListBoxItem.ContentTemplateProperty],
  215. });
  216. }
  217. private FuncControlTemplate ScrollViewerTemplate()
  218. {
  219. return new FuncControlTemplate<ScrollViewer>(parent =>
  220. new ScrollContentPresenter
  221. {
  222. Name = "PART_ContentPresenter",
  223. [~ScrollContentPresenter.ContentProperty] = parent.GetObservable(ScrollViewer.ContentProperty).ToBinding(),
  224. [~~ScrollContentPresenter.ExtentProperty] = parent[~~ScrollViewer.ExtentProperty],
  225. [~~ScrollContentPresenter.OffsetProperty] = parent[~~ScrollViewer.OffsetProperty],
  226. [~~ScrollContentPresenter.ViewportProperty] = parent[~~ScrollViewer.ViewportProperty],
  227. });
  228. }
  229. private void Prepare(ListBox target)
  230. {
  231. // The ListBox needs to be part of a rooted visual tree.
  232. var root = new TestRoot();
  233. root.Child = target;
  234. // Apply the template to the ListBox itself.
  235. target.ApplyTemplate();
  236. // Then to its inner ScrollViewer.
  237. var scrollViewer = (ScrollViewer)target.GetVisualChildren().Single();
  238. scrollViewer.ApplyTemplate();
  239. // Then make the ScrollViewer create its child.
  240. ((ContentPresenter)scrollViewer.Presenter).UpdateChild();
  241. // Now the ItemsPresenter should be reigstered, so apply its template.
  242. target.Presenter.ApplyTemplate();
  243. // Because ListBox items are virtualized we need to do a layout to make them appear.
  244. target.Measure(new Size(100, 100));
  245. target.Arrange(new Rect(0, 0, 100, 100));
  246. // Now set and apply the item templates.
  247. foreach (ListBoxItem item in target.Presenter.Panel.Children)
  248. {
  249. item.Template = ListBoxItemTemplate();
  250. item.ApplyTemplate();
  251. item.Presenter.ApplyTemplate();
  252. ((ContentPresenter)item.Presenter).UpdateChild();
  253. }
  254. // The items were created before the template was applied, so now we need to go back
  255. // and re-arrange everything.
  256. foreach (IControl i in target.GetSelfAndVisualDescendants())
  257. {
  258. i.InvalidateMeasure();
  259. }
  260. target.Measure(new Size(100, 100));
  261. target.Arrange(new Rect(0, 0, 100, 100));
  262. }
  263. private class Item
  264. {
  265. public Item(string value)
  266. {
  267. Value = value;
  268. }
  269. public string Value { get; }
  270. }
  271. }
  272. }