ItemsPresenterTests_Virtualization.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  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.Collections.Generic;
  4. using System.Linq;
  5. using Avalonia.Controls.Generators;
  6. using Avalonia.Controls.Presenters;
  7. using Avalonia.Controls.Primitives;
  8. using Avalonia.Controls.Templates;
  9. using Xunit;
  10. namespace Avalonia.Controls.UnitTests.Presenters
  11. {
  12. public class ItemsPresenterTests_Virtualization
  13. {
  14. [Fact]
  15. public void Should_Return_IsLogicalScrollEnabled_False_When_Has_No_Virtualizing_Panel()
  16. {
  17. var target = CreateTarget();
  18. target.ClearValue(ItemsPresenter.ItemsPanelProperty);
  19. target.ApplyTemplate();
  20. Assert.False(((ILogicalScrollable)target).IsLogicalScrollEnabled);
  21. }
  22. [Fact]
  23. public void Should_Return_IsLogicalScrollEnabled_False_When_VirtualizationMode_None()
  24. {
  25. var target = CreateTarget(ItemVirtualizationMode.None);
  26. target.ApplyTemplate();
  27. Assert.False(((ILogicalScrollable)target).IsLogicalScrollEnabled);
  28. }
  29. [Fact]
  30. public void Should_Return_IsLogicalScrollEnabled_False_When_Doesnt_Have_ScrollPresenter_Parent()
  31. {
  32. var target = new ItemsPresenter
  33. {
  34. ItemsPanel = VirtualizingPanelTemplate(),
  35. ItemTemplate = ItemTemplate(),
  36. VirtualizationMode = ItemVirtualizationMode.Simple,
  37. };
  38. target.ApplyTemplate();
  39. Assert.False(((ILogicalScrollable)target).IsLogicalScrollEnabled);
  40. }
  41. [Fact]
  42. public void Should_Return_IsLogicalScrollEnabled_True()
  43. {
  44. var target = CreateTarget();
  45. target.ApplyTemplate();
  46. Assert.True(((ILogicalScrollable)target).IsLogicalScrollEnabled);
  47. }
  48. [Fact]
  49. public void Should_Fill_Panel_With_Containers()
  50. {
  51. var target = CreateTarget();
  52. target.ApplyTemplate();
  53. target.Measure(new Size(100, 100));
  54. target.Arrange(new Rect(0, 0, 100, 100));
  55. Assert.Equal(10, target.Panel.Children.Count);
  56. }
  57. [Fact]
  58. public void Should_Only_Create_Enough_Containers_To_Display_All_Items()
  59. {
  60. var target = CreateTarget(itemCount: 2);
  61. target.ApplyTemplate();
  62. target.Measure(new Size(100, 100));
  63. target.Arrange(new Rect(0, 0, 100, 100));
  64. Assert.Equal(2, target.Panel.Children.Count);
  65. }
  66. [Fact]
  67. public void Initial_Item_DataContexts_Should_Be_Correct()
  68. {
  69. var target = CreateTarget();
  70. var items = (IList<string>)target.Items;
  71. target.ApplyTemplate();
  72. target.Measure(new Size(100, 100));
  73. target.Arrange(new Rect(0, 0, 100, 100));
  74. for (var i = 0; i < target.Panel.Children.Count; ++i)
  75. {
  76. Assert.Equal(items[i], target.Panel.Children[i].DataContext);
  77. }
  78. }
  79. [Fact]
  80. public void Should_Add_New_Items_When_Control_Is_Enlarged()
  81. {
  82. var target = CreateTarget();
  83. var items = (IList<string>)target.Items;
  84. target.ApplyTemplate();
  85. target.Measure(new Size(100, 100));
  86. target.Arrange(new Rect(0, 0, 100, 100));
  87. Assert.Equal(10, target.Panel.Children.Count);
  88. target.Arrange(new Rect(0, 0, 100, 120));
  89. Assert.Equal(12, target.Panel.Children.Count);
  90. for (var i = 0; i < target.Panel.Children.Count; ++i)
  91. {
  92. Assert.Equal(items[i], target.Panel.Children[i].DataContext);
  93. }
  94. }
  95. public class Simple
  96. {
  97. [Fact]
  98. public void Should_Return_Items_Count_For_Extent_Vertical()
  99. {
  100. var target = CreateTarget();
  101. target.ApplyTemplate();
  102. Assert.Equal(new Size(0, 20), ((ILogicalScrollable)target).Extent);
  103. }
  104. [Fact]
  105. public void Should_Return_Items_Count_For_Extent_Horizontal()
  106. {
  107. var target = CreateTarget(orientation: Orientation.Horizontal);
  108. target.ApplyTemplate();
  109. Assert.Equal(new Size(20, 0), ((ILogicalScrollable)target).Extent);
  110. }
  111. [Fact]
  112. public void Should_Have_Number_Of_Visible_Items_As_Viewport_Vertical()
  113. {
  114. var target = CreateTarget();
  115. target.ApplyTemplate();
  116. target.Measure(new Size(100, 100));
  117. target.Arrange(new Rect(0, 0, 100, 100));
  118. Assert.Equal(new Size(0, 10), ((ILogicalScrollable)target).Viewport);
  119. }
  120. [Fact]
  121. public void Should_Have_Number_Of_Visible_Items_As_Viewport_Horizontal()
  122. {
  123. var target = CreateTarget(orientation: Orientation.Horizontal);
  124. target.ApplyTemplate();
  125. target.Measure(new Size(100, 100));
  126. target.Arrange(new Rect(0, 0, 100, 100));
  127. Assert.Equal(new Size(10, 0), ((ILogicalScrollable)target).Viewport);
  128. }
  129. [Fact]
  130. public void Should_Remove_Items_When_Control_Is_Shrank()
  131. {
  132. var target = CreateTarget();
  133. var items = (IList<string>)target.Items;
  134. target.ApplyTemplate();
  135. target.Measure(new Size(100, 100));
  136. target.Arrange(new Rect(0, 0, 100, 100));
  137. Assert.Equal(10, target.Panel.Children.Count);
  138. target.Arrange(new Rect(0, 0, 100, 80));
  139. Assert.Equal(8, target.Panel.Children.Count);
  140. }
  141. [Fact]
  142. public void Should_Update_Containers_When_Items_Changes()
  143. {
  144. var target = CreateTarget();
  145. target.ApplyTemplate();
  146. target.Measure(new Size(100, 100));
  147. target.Arrange(new Rect(0, 0, 100, 100));
  148. target.Items = new[] { "foo", "bar", "baz" };
  149. Assert.Equal(3, target.Panel.Children.Count);
  150. }
  151. [Fact]
  152. public void Should_Decrease_The_Viewport_Size_By_One_If_There_Is_A_Partial_Item()
  153. {
  154. var target = CreateTarget();
  155. target.ApplyTemplate();
  156. target.Measure(new Size(100, 95));
  157. target.Arrange(new Rect(0, 0, 100, 95));
  158. Assert.Equal(new Size(0, 9), ((ILogicalScrollable)target).Viewport);
  159. }
  160. [Fact]
  161. public void Moving_To_And_From_The_End_With_Partial_Item_Should_Set_Panel_PixelOffset()
  162. {
  163. var target = CreateTarget(itemCount: 20);
  164. target.ApplyTemplate();
  165. target.Measure(new Size(100, 95));
  166. target.Arrange(new Rect(0, 0, 100, 95));
  167. ((ILogicalScrollable)target).Offset = new Vector(0, 11);
  168. var minIndex = target.ItemContainerGenerator.Containers.Min(x => x.Index);
  169. Assert.Equal(new Vector(0, 11), ((ILogicalScrollable)target).Offset);
  170. Assert.Equal(10, minIndex);
  171. Assert.Equal(5, ((IVirtualizingPanel)target.Panel).PixelOffset);
  172. ((ILogicalScrollable)target).Offset = new Vector(0, 10);
  173. minIndex = target.ItemContainerGenerator.Containers.Min(x => x.Index);
  174. Assert.Equal(new Vector(0, 10), ((ILogicalScrollable)target).Offset);
  175. Assert.Equal(10, minIndex);
  176. Assert.Equal(0, ((IVirtualizingPanel)target.Panel).PixelOffset);
  177. ((ILogicalScrollable)target).Offset = new Vector(0, 11);
  178. minIndex = target.ItemContainerGenerator.Containers.Min(x => x.Index);
  179. Assert.Equal(new Vector(0, 11), ((ILogicalScrollable)target).Offset);
  180. Assert.Equal(10, minIndex);
  181. Assert.Equal(5, ((IVirtualizingPanel)target.Panel).PixelOffset);
  182. }
  183. public class WithContainers
  184. {
  185. [Fact]
  186. public void Scrolling_Less_Than_A_Page_Should_Move_Recycled_Items()
  187. {
  188. var target = CreateTarget();
  189. var items = (IList<string>)target.Items;
  190. target.ApplyTemplate();
  191. target.Measure(new Size(100, 100));
  192. target.Arrange(new Rect(0, 0, 100, 100));
  193. var containers = target.Panel.Children.ToList();
  194. var scroller = (ScrollContentPresenter)target.Parent;
  195. scroller.Offset = new Vector(0, 5);
  196. var scrolledContainers = containers
  197. .Skip(5)
  198. .Take(5)
  199. .Concat(containers.Take(5)).ToList();
  200. Assert.Equal(new Vector(0, 5), ((ILogicalScrollable)target).Offset);
  201. Assert.Equal(scrolledContainers, target.Panel.Children);
  202. for (var i = 0; i < target.Panel.Children.Count; ++i)
  203. {
  204. Assert.Equal(items[i + 5], target.Panel.Children[i].DataContext);
  205. }
  206. scroller.Offset = new Vector(0, 0);
  207. Assert.Equal(new Vector(0, 0), ((ILogicalScrollable)target).Offset);
  208. Assert.Equal(containers, target.Panel.Children);
  209. var dcs = target.Panel.Children.Select(x => x.DataContext).ToList();
  210. for (var i = 0; i < target.Panel.Children.Count; ++i)
  211. {
  212. Assert.Equal(items[i], target.Panel.Children[i].DataContext);
  213. }
  214. }
  215. [Fact]
  216. public void Scrolling_More_Than_A_Page_Should_Recycle_Items()
  217. {
  218. var target = CreateTarget(itemCount: 50);
  219. var items = (IList<string>)target.Items;
  220. target.ApplyTemplate();
  221. target.Measure(new Size(100, 100));
  222. target.Arrange(new Rect(0, 0, 100, 100));
  223. var containers = target.Panel.Children.ToList();
  224. var scroller = (ScrollContentPresenter)target.Parent;
  225. scroller.Offset = new Vector(0, 20);
  226. Assert.Equal(new Vector(0, 20), ((ILogicalScrollable)target).Offset);
  227. Assert.Equal(containers, target.Panel.Children);
  228. for (var i = 0; i < target.Panel.Children.Count; ++i)
  229. {
  230. Assert.Equal(items[i + 20], target.Panel.Children[i].DataContext);
  231. }
  232. scroller.Offset = new Vector(0, 0);
  233. Assert.Equal(new Vector(0, 0), ((ILogicalScrollable)target).Offset);
  234. Assert.Equal(containers, target.Panel.Children);
  235. for (var i = 0; i < target.Panel.Children.Count; ++i)
  236. {
  237. Assert.Equal(items[i], target.Panel.Children[i].DataContext);
  238. }
  239. }
  240. }
  241. }
  242. private static ItemsPresenter CreateTarget(
  243. ItemVirtualizationMode mode = ItemVirtualizationMode.Simple,
  244. Orientation orientation = Orientation.Vertical,
  245. bool useContainers = true,
  246. int itemCount = 20)
  247. {
  248. ItemsPresenter result;
  249. var items = Enumerable.Range(0, itemCount).Select(x => $"Item {x}").ToList();
  250. var scroller = new ScrollContentPresenter
  251. {
  252. Content = result = new TestItemsPresenter(useContainers)
  253. {
  254. Items = items,
  255. ItemsPanel = VirtualizingPanelTemplate(orientation),
  256. ItemTemplate = ItemTemplate(),
  257. VirtualizationMode = mode,
  258. }
  259. };
  260. scroller.UpdateChild();
  261. return result;
  262. }
  263. private static IDataTemplate ItemTemplate()
  264. {
  265. return new FuncDataTemplate<string>(x => new Canvas
  266. {
  267. Width = 10,
  268. Height = 10,
  269. });
  270. }
  271. private static ITemplate<IPanel> VirtualizingPanelTemplate(
  272. Orientation orientation = Orientation.Vertical)
  273. {
  274. return new FuncTemplate<IPanel>(() => new VirtualizingStackPanel
  275. {
  276. Orientation = orientation,
  277. });
  278. }
  279. private class TestItemsPresenter : ItemsPresenter
  280. {
  281. private bool _useContainers;
  282. public TestItemsPresenter(bool useContainers)
  283. {
  284. _useContainers = useContainers;
  285. }
  286. protected override IItemContainerGenerator CreateItemContainerGenerator()
  287. {
  288. return _useContainers ?
  289. new ItemContainerGenerator<TestContainer>(this, TestContainer.ContentProperty, null) :
  290. new ItemContainerGenerator(this);
  291. }
  292. }
  293. private class TestContainer : ContentControl
  294. {
  295. public TestContainer()
  296. {
  297. Width = 10;
  298. Height = 10;
  299. }
  300. }
  301. }
  302. }