ItemsPresenterTests_Virtualization_Simple.cs 14 KB

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