ItemsPresenterTests_Virtualization_Simple.cs 38 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085
  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;
  5. using System.Collections.Generic;
  6. using System.Collections.ObjectModel;
  7. using System.Linq;
  8. using Avalonia.Collections;
  9. using Avalonia.Controls.Generators;
  10. using Avalonia.Controls.Presenters;
  11. using Avalonia.Controls.Primitives;
  12. using Avalonia.Controls.Templates;
  13. using Avalonia.Input;
  14. using Avalonia.Platform;
  15. using Avalonia.Rendering;
  16. using Avalonia.UnitTests;
  17. using Xunit;
  18. namespace Avalonia.Controls.UnitTests.Presenters
  19. {
  20. public class ItemsPresenterTests_Virtualization_Simple
  21. {
  22. [Fact]
  23. public void Should_Return_Items_Count_For_Extent_Vertical()
  24. {
  25. var target = CreateTarget();
  26. target.ApplyTemplate();
  27. Assert.Equal(new Size(0, 20), ((ILogicalScrollable)target).Extent);
  28. }
  29. [Fact]
  30. public void Should_Return_Items_Count_For_Extent_Horizontal()
  31. {
  32. var target = CreateTarget(orientation: Orientation.Horizontal);
  33. target.ApplyTemplate();
  34. Assert.Equal(new Size(20, 0), ((ILogicalScrollable)target).Extent);
  35. }
  36. [Fact]
  37. public void Should_Have_Number_Of_Visible_Items_As_Viewport_Vertical()
  38. {
  39. var target = CreateTarget();
  40. target.ApplyTemplate();
  41. target.Measure(new Size(100, 100));
  42. target.Arrange(new Rect(0, 0, 100, 100));
  43. Assert.Equal(new Size(100, 10), ((ILogicalScrollable)target).Viewport);
  44. }
  45. [Fact]
  46. public void Should_Have_Number_Of_Visible_Items_As_Viewport_Horizontal()
  47. {
  48. var target = CreateTarget(orientation: Orientation.Horizontal);
  49. target.ApplyTemplate();
  50. target.Measure(new Size(100, 100));
  51. target.Arrange(new Rect(0, 0, 100, 100));
  52. Assert.Equal(new Size(10, 100), ((ILogicalScrollable)target).Viewport);
  53. }
  54. [Fact]
  55. public void Should_Add_Containers_When_Panel_Is_Not_Full()
  56. {
  57. var target = CreateTarget(itemCount: 5);
  58. var items = (IList<string>)target.Items;
  59. target.ApplyTemplate();
  60. target.Measure(new Size(100, 100));
  61. target.Arrange(new Rect(0, 0, 100, 100));
  62. Assert.Equal(5, target.Panel.Children.Count);
  63. items.Add("New Item");
  64. Assert.Equal(6, target.Panel.Children.Count);
  65. }
  66. [Fact]
  67. public void Should_Remove_Items_When_Control_Is_Shrank()
  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. Assert.Equal(10, target.Panel.Children.Count);
  75. target.Measure(new Size(100, 80));
  76. target.Arrange(new Rect(0, 0, 100, 80));
  77. Assert.Equal(8, target.Panel.Children.Count);
  78. }
  79. [Fact]
  80. public void Should_Add_New_Containers_At_Top_When_Control_Is_Scrolled_To_Bottom_And_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. ((IScrollable)target).Offset = new Vector(0, 10);
  89. target.Measure(new Size(120, 120));
  90. target.Arrange(new Rect(0, 0, 100, 120));
  91. Assert.Equal(12, target.Panel.Children.Count);
  92. for (var i = 0; i < target.Panel.Children.Count; ++i)
  93. {
  94. Assert.Equal(items[i + 8], target.Panel.Children[i].DataContext);
  95. }
  96. }
  97. [Fact]
  98. public void Should_Update_Containers_When_Items_Changes()
  99. {
  100. var target = CreateTarget();
  101. target.ApplyTemplate();
  102. target.Measure(new Size(100, 100));
  103. target.Arrange(new Rect(0, 0, 100, 100));
  104. target.Items = new[] { "foo", "bar", "baz" };
  105. Assert.Equal(3, target.Panel.Children.Count);
  106. }
  107. [Fact]
  108. public void Should_Decrease_The_Viewport_Size_By_One_If_There_Is_A_Partial_Item()
  109. {
  110. var target = CreateTarget();
  111. target.ApplyTemplate();
  112. target.Measure(new Size(100, 95));
  113. target.Arrange(new Rect(0, 0, 100, 95));
  114. Assert.Equal(new Size(100, 9), ((ILogicalScrollable)target).Viewport);
  115. }
  116. [Fact]
  117. public void Moving_To_And_From_The_End_With_Partial_Item_Should_Set_Panel_PixelOffset()
  118. {
  119. var target = CreateTarget();
  120. target.ApplyTemplate();
  121. target.Measure(new Size(100, 95));
  122. target.Arrange(new Rect(0, 0, 100, 95));
  123. ((ILogicalScrollable)target).Offset = new Vector(0, 11);
  124. var minIndex = target.ItemContainerGenerator.Containers.Min(x => x.Index);
  125. Assert.Equal(new Vector(0, 11), ((ILogicalScrollable)target).Offset);
  126. Assert.Equal(10, minIndex);
  127. Assert.Equal(10, ((IVirtualizingPanel)target.Panel).PixelOffset);
  128. ((ILogicalScrollable)target).Offset = new Vector(0, 10);
  129. minIndex = target.ItemContainerGenerator.Containers.Min(x => x.Index);
  130. Assert.Equal(new Vector(0, 10), ((ILogicalScrollable)target).Offset);
  131. Assert.Equal(10, minIndex);
  132. Assert.Equal(0, ((IVirtualizingPanel)target.Panel).PixelOffset);
  133. ((ILogicalScrollable)target).Offset = new Vector(0, 11);
  134. minIndex = target.ItemContainerGenerator.Containers.Min(x => x.Index);
  135. Assert.Equal(new Vector(0, 11), ((ILogicalScrollable)target).Offset);
  136. Assert.Equal(10, minIndex);
  137. Assert.Equal(10, ((IVirtualizingPanel)target.Panel).PixelOffset);
  138. }
  139. [Fact]
  140. public void Inserting_Items_Should_Update_Containers()
  141. {
  142. var target = CreateTarget();
  143. target.ApplyTemplate();
  144. target.Measure(new Size(100, 100));
  145. target.Arrange(new Rect(0, 0, 100, 100));
  146. ((ILogicalScrollable)target).Offset = new Vector(0, 5);
  147. var expected = Enumerable.Range(5, 10).Select(x => $"Item {x}").ToList();
  148. var items = (ObservableCollection<string>)target.Items;
  149. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  150. Assert.Equal(expected, actual);
  151. items.Insert(6, "Inserted");
  152. expected.Insert(1, "Inserted");
  153. expected.RemoveAt(expected.Count - 1);
  154. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  155. Assert.Equal(expected, actual);
  156. }
  157. [Fact]
  158. public void Inserting_Items_Before_Visibile_Containers_Should_Update_Containers()
  159. {
  160. var target = CreateTarget();
  161. target.ApplyTemplate();
  162. target.Measure(new Size(100, 100));
  163. target.Arrange(new Rect(0, 0, 100, 100));
  164. ((ILogicalScrollable)target).Offset = new Vector(0, 5);
  165. var expected = Enumerable.Range(5, 10).Select(x => $"Item {x}").ToList();
  166. var items = (ObservableCollection<string>)target.Items;
  167. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  168. Assert.Equal(expected, actual);
  169. items.Insert(0, "Inserted");
  170. expected = Enumerable.Range(4, 10).Select(x => $"Item {x}").ToList();
  171. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  172. Assert.Equal(expected, actual);
  173. }
  174. [Fact]
  175. public void Removing_First_Materialized_Item_Should_Update_Containers()
  176. {
  177. var target = CreateTarget();
  178. target.ApplyTemplate();
  179. target.Measure(new Size(100, 100));
  180. target.Arrange(new Rect(0, 0, 100, 100));
  181. var expected = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToList();
  182. var items = (ObservableCollection<string>)target.Items;
  183. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  184. Assert.Equal(expected, actual);
  185. items.RemoveAt(0);
  186. expected = Enumerable.Range(1, 10).Select(x => $"Item {x}").ToList();
  187. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  188. Assert.Equal(expected, actual);
  189. }
  190. [Fact]
  191. public void Removing_Items_From_Middle_Should_Update_Containers_When_All_Items_Visible()
  192. {
  193. var target = CreateTarget();
  194. target.ApplyTemplate();
  195. target.Measure(new Size(100, 200));
  196. target.Arrange(new Rect(0, 0, 100, 200));
  197. var items = (ObservableCollection<string>)target.Items;
  198. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  199. Assert.Equal(items, actual);
  200. items.RemoveAt(2);
  201. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  202. Assert.Equal(items, actual);
  203. items.RemoveAt(items.Count - 2);
  204. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  205. Assert.Equal(items, actual);
  206. }
  207. [Fact]
  208. public void Removing_Last_Item_Should_Update_Containers_When_All_Items_Visible()
  209. {
  210. var target = CreateTarget();
  211. target.ApplyTemplate();
  212. target.Measure(new Size(100, 200));
  213. target.Arrange(new Rect(0, 0, 100, 200));
  214. ((ILogicalScrollable)target).Offset = new Vector(0, 5);
  215. var expected = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList();
  216. var items = (ObservableCollection<string>)target.Items;
  217. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  218. Assert.Equal(expected, actual);
  219. items.Remove(items.Last());
  220. expected.Remove(expected.Last());
  221. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  222. Assert.Equal(expected, actual);
  223. }
  224. [Fact]
  225. public void Removing_Items_When_Scrolled_To_End_Should_Recyle_Containers_At_Top()
  226. {
  227. var target = CreateTarget(useAvaloniaList: true);
  228. target.ApplyTemplate();
  229. target.Measure(new Size(100, 100));
  230. target.Arrange(new Rect(0, 0, 100, 100));
  231. ((ILogicalScrollable)target).Offset = new Vector(0, 10);
  232. var expected = Enumerable.Range(10, 10).Select(x => $"Item {x}").ToList();
  233. var items = (AvaloniaList<string>)target.Items;
  234. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  235. Assert.Equal(expected, actual);
  236. items.RemoveRange(18, 2);
  237. expected = Enumerable.Range(8, 10).Select(x => $"Item {x}").ToList();
  238. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  239. Assert.Equal(expected, actual);
  240. }
  241. [Fact]
  242. public void Removing_Items_When_Scrolled_To_Near_End_Should_Recycle_Containers_At_Bottom_And_Top()
  243. {
  244. var target = CreateTarget(useAvaloniaList: true);
  245. target.ApplyTemplate();
  246. target.Measure(new Size(100, 100));
  247. target.Arrange(new Rect(0, 0, 100, 100));
  248. ((ILogicalScrollable)target).Offset = new Vector(0, 9);
  249. var expected = Enumerable.Range(9, 10).Select(x => $"Item {x}").ToList();
  250. var items = (AvaloniaList<string>)target.Items;
  251. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  252. Assert.Equal(expected, actual);
  253. items.RemoveRange(15, 3);
  254. expected = Enumerable.Range(7, 8).Select(x => $"Item {x}")
  255. .Concat(Enumerable.Range(18, 2).Select(x => $"Item {x}"))
  256. .ToList();
  257. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  258. Assert.Equal(expected, actual);
  259. }
  260. [Fact]
  261. public void Measuring_To_Infinity_When_Scrolled_To_End_Should_Not_Throw()
  262. {
  263. var target = CreateTarget(useAvaloniaList: true);
  264. target.ApplyTemplate();
  265. target.Measure(new Size(100, 100));
  266. target.Arrange(new Rect(0, 0, 100, 100));
  267. ((ILogicalScrollable)target).Offset = new Vector(0, 10);
  268. // Check for issue #589: this should not throw.
  269. target.Measure(Size.Infinity);
  270. var expected = Enumerable.Range(0, 20).Select(x => $"Item {x}").ToList();
  271. var items = (AvaloniaList<string>)target.Items;
  272. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  273. Assert.Equal(expected, actual);
  274. }
  275. [Fact]
  276. public void Replacing_Items_Should_Update_Containers()
  277. {
  278. var target = CreateTarget();
  279. target.ApplyTemplate();
  280. target.Measure(new Size(100, 100));
  281. target.Arrange(new Rect(0, 0, 100, 100));
  282. var expected = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToList();
  283. var items = (ObservableCollection<string>)target.Items;
  284. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  285. Assert.Equal(expected, actual);
  286. items[4] = expected[4] = "Replaced";
  287. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  288. Assert.Equal(expected, actual);
  289. }
  290. [Fact]
  291. public void Moving_Items_Should_Update_Containers()
  292. {
  293. var target = CreateTarget();
  294. target.ApplyTemplate();
  295. target.Measure(new Size(100, 100));
  296. target.Arrange(new Rect(0, 0, 100, 100));
  297. var expected = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToList();
  298. var items = (ObservableCollection<string>)target.Items;
  299. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  300. Assert.Equal(expected, actual);
  301. items.Move(4, 8);
  302. var i = expected[4];
  303. expected.RemoveAt(4);
  304. expected.Insert(8, i);
  305. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  306. Assert.Equal(expected, actual);
  307. }
  308. [Fact]
  309. public void Setting_Items_To_Null_Should_Remove_Containers()
  310. {
  311. var target = CreateTarget();
  312. target.ApplyTemplate();
  313. target.Measure(new Size(100, 100));
  314. target.Arrange(new Rect(0, 0, 100, 100));
  315. var expected = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToList();
  316. var items = (ObservableCollection<string>)target.Items;
  317. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  318. Assert.Equal(expected, actual);
  319. target.Items = null;
  320. Assert.Empty(target.Panel.Children);
  321. }
  322. [Fact]
  323. public void Reassigning_Items_Should_Create_Containers()
  324. {
  325. var target = CreateTarget(itemCount: 5);
  326. target.ApplyTemplate();
  327. target.Measure(new Size(100, 100));
  328. target.Arrange(new Rect(0, 0, 100, 100));
  329. var expected = Enumerable.Range(0, 5).Select(x => $"Item {x}").ToList();
  330. var items = (ObservableCollection<string>)target.Items;
  331. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  332. Assert.Equal(expected, actual);
  333. expected = Enumerable.Range(0, 6).Select(x => $"Item {x}").ToList();
  334. target.Items = expected;
  335. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  336. Assert.Equal(expected, actual);
  337. }
  338. [Fact]
  339. public void Inserting_Then_Removing_Should_Add_Remove_Containers()
  340. {
  341. var items = new AvaloniaList<string>(Enumerable.Range(0, 5).Select(x => $"Item {x}"));
  342. var toAdd = Enumerable.Range(0, 3).Select(x => $"Added Item {x}").ToArray();
  343. var target = new ItemsPresenter
  344. {
  345. VirtualizationMode = ItemVirtualizationMode.None,
  346. Items = items,
  347. ItemTemplate = new FuncDataTemplate<string>(x => new TextBlock { Height = 10 }),
  348. };
  349. target.ApplyTemplate();
  350. Assert.Equal(items.Count, target.Panel.Children.Count);
  351. int addIndex = 1;
  352. foreach (var item in toAdd)
  353. {
  354. items.Insert(addIndex++, item);
  355. }
  356. Assert.Equal(items.Count, target.Panel.Children.Count);
  357. foreach (var item in toAdd)
  358. {
  359. items.Remove(item);
  360. }
  361. Assert.Equal(items.Count, target.Panel.Children.Count);
  362. }
  363. [Fact]
  364. public void Reassigning_Items_Should_Remove_Containers()
  365. {
  366. var target = CreateTarget(itemCount: 6);
  367. target.ApplyTemplate();
  368. target.Measure(new Size(100, 100));
  369. target.Arrange(new Rect(0, 0, 100, 100));
  370. var expected = Enumerable.Range(0, 6).Select(x => $"Item {x}").ToList();
  371. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  372. Assert.Equal(expected, actual);
  373. expected = Enumerable.Range(0, 5).Select(x => $"Item {x}").ToList();
  374. target.Items = expected;
  375. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  376. Assert.Equal(expected, actual);
  377. }
  378. [Fact]
  379. public void Clearing_Items_And_ReAdding_Should_Remove_Containers()
  380. {
  381. var target = CreateTarget(itemCount: 6);
  382. target.ApplyTemplate();
  383. target.Measure(new Size(100, 100));
  384. target.Arrange(new Rect(0, 0, 100, 100));
  385. var expected = Enumerable.Range(0, 6).Select(x => $"Item {x}").ToList();
  386. var items = (ObservableCollection<string>)target.Items;
  387. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  388. Assert.Equal(expected, actual);
  389. expected = Enumerable.Range(0, 5).Select(x => $"Item {x}").ToList();
  390. target.Items = null;
  391. target.Items = expected;
  392. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  393. Assert.Equal(expected, actual);
  394. }
  395. [Fact]
  396. public void Scrolling_To_Partial_Last_Item_Then_Adding_Item_Updates_Containers()
  397. {
  398. var target = CreateTarget(itemCount: 10);
  399. var items = (IList<string>)target.Items;
  400. target.ApplyTemplate();
  401. target.Measure(new Size(100, 95));
  402. target.Arrange(new Rect(0, 0, 100, 95));
  403. ((ILogicalScrollable)target).Offset = new Vector(0, 1);
  404. Assert.Equal(new Vector(0, 1), ((ILogicalScrollable)target).Offset);
  405. var expected = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToList();
  406. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  407. Assert.Equal(expected, actual);
  408. Assert.Equal(10, ((IVirtualizingPanel)target.Panel).PixelOffset);
  409. items.Add("Item 10");
  410. expected = Enumerable.Range(1, 10).Select(x => $"Item {x}").ToList();
  411. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  412. Assert.Equal(expected, actual);
  413. Assert.Equal(0, ((IVirtualizingPanel)target.Panel).PixelOffset);
  414. }
  415. [Fact]
  416. public void Scrolling_To_Item_In_Zero_Sized_Presenter_Doesnt_Throw()
  417. {
  418. using (UnitTestApplication.Start(TestServices.RealLayoutManager))
  419. {
  420. var target = CreateTarget(itemCount: 10);
  421. var items = (IList<string>)target.Items;
  422. target.ApplyTemplate();
  423. target.Measure(Size.Empty);
  424. target.Arrange(Rect.Empty);
  425. // Check for issue #591: this should not throw.
  426. target.ScrollIntoView(items[0]);
  427. }
  428. }
  429. [Fact]
  430. public void InsertRange_Items_Should_Update_Containers()
  431. {
  432. var target = CreateTarget(useAvaloniaList: true);
  433. target.ApplyTemplate();
  434. target.Measure(new Size(100, 100));
  435. target.Arrange(new Rect(0, 0, 100, 100));
  436. var expected = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToList();
  437. var items = (AvaloniaList<string>)target.Items;
  438. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  439. Assert.Equal(expected, actual);
  440. var toAdd = Enumerable.Range(0, 3).Select(x => $"New Item {x}").ToList();
  441. int index = 1;
  442. items.InsertRange(index, toAdd);
  443. expected.InsertRange(index, toAdd);
  444. expected.RemoveRange(10, toAdd.Count);
  445. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  446. Assert.Equal(expected, actual);
  447. }
  448. [Fact]
  449. public void InsertRange_Items_Before_Last_Should_Update_Containers()
  450. {
  451. var target = CreateTarget(useAvaloniaList: true);
  452. target.ApplyTemplate();
  453. target.Measure(new Size(100, 100));
  454. target.Arrange(new Rect(0, 0, 100, 100));
  455. var expected = Enumerable.Range(0, 10).Select(x => $"Item {x}").ToList();
  456. var items = (AvaloniaList<string>)target.Items;
  457. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  458. Assert.Equal(expected, actual);
  459. var toAdd = Enumerable.Range(0, 3).Select(x => $"New Item {x}").ToList();
  460. int index = 8;
  461. items.InsertRange(index, toAdd);
  462. expected.InsertRange(index, toAdd);
  463. expected.RemoveRange(10, toAdd.Count);
  464. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  465. Assert.Equal(expected, actual);
  466. }
  467. [Fact]
  468. public void RemoveRange_Items_Should_Update_Containers()
  469. {
  470. var target = CreateTarget(useAvaloniaList: true);
  471. target.ApplyTemplate();
  472. target.Measure(new Size(100, 100));
  473. target.Arrange(new Rect(0, 0, 100, 100));
  474. var expected = Enumerable.Range(0, 13).Select(x => $"Item {x}").ToList();
  475. var items = (AvaloniaList<string>)target.Items;
  476. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  477. Assert.Equal(expected.Take(10), actual);
  478. int index = 5;
  479. int count = 3;
  480. items.RemoveRange(index, count);
  481. expected.RemoveRange(index, count);
  482. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  483. Assert.Equal(expected, actual);
  484. }
  485. [Fact]
  486. public void RemoveRange_Items_Before_Last_Should_Update_Containers()
  487. {
  488. var target = CreateTarget(useAvaloniaList: true);
  489. target.ApplyTemplate();
  490. target.Measure(new Size(100, 100));
  491. target.Arrange(new Rect(0, 0, 100, 100));
  492. var expected = Enumerable.Range(0, 13).Select(x => $"Item {x}").ToList();
  493. var items = (AvaloniaList<string>)target.Items;
  494. var actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  495. Assert.Equal(expected.Take(10), actual);
  496. int index = 8;
  497. int count = 3;
  498. items.RemoveRange(index, count);
  499. expected.RemoveRange(index, count);
  500. actual = target.Panel.Children.Select(x => x.DataContext).ToList();
  501. Assert.Equal(expected, actual);
  502. }
  503. [Fact]
  504. public void Should_Add_Containers_For_Items_After_Clear()
  505. {
  506. var target = CreateTarget(itemCount: 10);
  507. var defaultItems = (IList<string>)target.Items;
  508. var items = new AvaloniaList<string>(defaultItems);
  509. target.Items = items;
  510. target.ApplyTemplate();
  511. target.Measure(new Size(100, 100));
  512. target.Arrange(new Rect(target.DesiredSize));
  513. Assert.Equal(10, target.Panel.Children.Count);
  514. items.Clear();
  515. target.Panel.Measure(new Size(100, 100));
  516. target.Panel.Arrange(new Rect(target.Panel.DesiredSize));
  517. target.Measure(new Size(100, 100));
  518. target.Arrange(new Rect(target.DesiredSize));
  519. Assert.Equal(0, target.Panel.Children.Count);
  520. items.AddRange(defaultItems.Select(s => s + " new"));
  521. target.Panel.Measure(new Size(100, 100));
  522. target.Panel.Arrange(new Rect(target.Panel.DesiredSize));
  523. target.Measure(new Size(100, 100));
  524. target.Arrange(new Rect(target.DesiredSize));
  525. Assert.Equal(10, target.Panel.Children.Count);
  526. }
  527. public class Vertical
  528. {
  529. [Fact]
  530. public void GetControlInDirection_Down_Should_Return_Existing_Container_If_Materialized()
  531. {
  532. var target = CreateTarget();
  533. target.ApplyTemplate();
  534. target.Measure(new Size(100, 100));
  535. target.Arrange(new Rect(0, 0, 100, 100));
  536. var from = target.Panel.Children[5];
  537. var result = ((ILogicalScrollable)target).GetControlInDirection(
  538. NavigationDirection.Down,
  539. from);
  540. Assert.Same(target.Panel.Children[6], result);
  541. }
  542. [Fact]
  543. public void GetControlInDirection_Down_Should_Scroll_If_Necessary()
  544. {
  545. var target = CreateTarget();
  546. target.ApplyTemplate();
  547. target.Measure(new Size(100, 100));
  548. target.Arrange(new Rect(0, 0, 100, 100));
  549. var from = target.Panel.Children[9];
  550. var result = ((ILogicalScrollable)target).GetControlInDirection(
  551. NavigationDirection.Down,
  552. from);
  553. Assert.Equal(new Vector(0, 1), ((ILogicalScrollable)target).Offset);
  554. Assert.Same(target.Panel.Children[9], result);
  555. }
  556. [Fact]
  557. public void GetControlInDirection_Down_Should_Scroll_If_Partially_Visible()
  558. {
  559. using (UnitTestApplication.Start(TestServices.RealLayoutManager))
  560. {
  561. var target = CreateTarget();
  562. var scroller = (ScrollContentPresenter)target.Parent;
  563. scroller.Measure(new Size(100, 95));
  564. scroller.Arrange(new Rect(0, 0, 100, 95));
  565. var from = target.Panel.Children[8];
  566. var result = ((ILogicalScrollable)target).GetControlInDirection(
  567. NavigationDirection.Down,
  568. from);
  569. Assert.Equal(new Vector(0, 1), ((ILogicalScrollable)target).Offset);
  570. Assert.Same(target.Panel.Children[8], result);
  571. }
  572. }
  573. [Fact]
  574. public void GetControlInDirection_Up_Should_Scroll_If_Partially_Visible_Item_Is_Currently_Shown()
  575. {
  576. using (UnitTestApplication.Start(TestServices.RealLayoutManager))
  577. {
  578. var target = CreateTarget();
  579. var scroller = (ScrollContentPresenter)target.Parent;
  580. scroller.Measure(new Size(100, 95));
  581. scroller.Arrange(new Rect(0, 0, 100, 95));
  582. ((ILogicalScrollable)target).Offset = new Vector(0, 11);
  583. var from = target.Panel.Children[1];
  584. var result = ((ILogicalScrollable)target).GetControlInDirection(
  585. NavigationDirection.Up,
  586. from);
  587. Assert.Equal(new Vector(0, 10), ((ILogicalScrollable)target).Offset);
  588. Assert.Same(target.Panel.Children[0], result);
  589. }
  590. }
  591. [Fact]
  592. public void Should_Return_Horizontal_Extent_And_Viewport()
  593. {
  594. var target = CreateTarget();
  595. target.ApplyTemplate();
  596. target.Measure(new Size(5, 100));
  597. target.Arrange(new Rect(0, 0, 5, 100));
  598. Assert.Equal(new Size(10, 20), ((ILogicalScrollable)target).Extent);
  599. Assert.Equal(new Size(5, 10), ((ILogicalScrollable)target).Viewport);
  600. }
  601. [Fact]
  602. public void Horizontal_Scroll_Should_Update_Item_Position()
  603. {
  604. var target = CreateTarget();
  605. target.ApplyTemplate();
  606. target.Measure(new Size(5, 100));
  607. target.Arrange(new Rect(0, 0, 5, 100));
  608. ((ILogicalScrollable)target).Offset = new Vector(5, 0);
  609. target.Measure(new Size(5, 100));
  610. target.Arrange(new Rect(0, 0, 5, 100));
  611. Assert.Equal(new Rect(-5, 0, 10, 10), target.Panel.Children[0].Bounds);
  612. }
  613. }
  614. public class Horizontal
  615. {
  616. [Fact]
  617. public void GetControlInDirection_Right_Should_Return_Existing_Container_If_Materialized()
  618. {
  619. var target = CreateTarget(orientation: Orientation.Horizontal);
  620. target.ApplyTemplate();
  621. target.Measure(new Size(100, 100));
  622. target.Arrange(new Rect(0, 0, 100, 100));
  623. var from = target.Panel.Children[5];
  624. var result = ((ILogicalScrollable)target).GetControlInDirection(
  625. NavigationDirection.Right,
  626. from);
  627. Assert.Same(target.Panel.Children[6], result);
  628. }
  629. [Fact]
  630. public void GetControlInDirection_Right_Should_Scroll_If_Necessary()
  631. {
  632. var target = CreateTarget(orientation: Orientation.Horizontal);
  633. target.ApplyTemplate();
  634. target.Measure(new Size(100, 100));
  635. target.Arrange(new Rect(0, 0, 100, 100));
  636. var from = target.Panel.Children[9];
  637. var result = ((ILogicalScrollable)target).GetControlInDirection(
  638. NavigationDirection.Right,
  639. from);
  640. Assert.Equal(new Vector(1, 0), ((ILogicalScrollable)target).Offset);
  641. Assert.Same(target.Panel.Children[9], result);
  642. }
  643. [Fact]
  644. public void GetControlInDirection_Right_Should_Scroll_If_Partially_Visible()
  645. {
  646. using (UnitTestApplication.Start(TestServices.RealLayoutManager))
  647. {
  648. var target = CreateTarget(orientation: Orientation.Horizontal);
  649. var scroller = (ScrollContentPresenter)target.Parent;
  650. scroller.Measure(new Size(95, 100));
  651. scroller.Arrange(new Rect(0, 0, 95, 100));
  652. var from = target.Panel.Children[8];
  653. var result = ((ILogicalScrollable)target).GetControlInDirection(
  654. NavigationDirection.Right,
  655. from);
  656. Assert.Equal(new Vector(1, 0), ((ILogicalScrollable)target).Offset);
  657. Assert.Same(target.Panel.Children[8], result);
  658. }
  659. }
  660. [Fact]
  661. public void GetControlInDirection_Left_Should_Scroll_If_Partially_Visible_Item_Is_Currently_Shown()
  662. {
  663. var target = CreateTarget(orientation: Orientation.Horizontal);
  664. target.ApplyTemplate();
  665. target.Measure(new Size(95, 100));
  666. target.Arrange(new Rect(0, 0, 95, 100));
  667. ((ILogicalScrollable)target).Offset = new Vector(11, 0);
  668. var from = target.Panel.Children[1];
  669. var result = ((ILogicalScrollable)target).GetControlInDirection(
  670. NavigationDirection.Left,
  671. from);
  672. Assert.Equal(new Vector(10, 0), ((ILogicalScrollable)target).Offset);
  673. Assert.Same(target.Panel.Children[0], result);
  674. }
  675. }
  676. public class WithContainers
  677. {
  678. [Fact]
  679. public void Scrolling_Less_Than_A_Page_Should_Move_Recycled_Items()
  680. {
  681. var target = CreateTarget();
  682. var items = (IList<string>)target.Items;
  683. target.ApplyTemplate();
  684. target.Measure(new Size(100, 100));
  685. target.Arrange(new Rect(0, 0, 100, 100));
  686. var containers = target.Panel.Children.ToList();
  687. var scroller = (ScrollContentPresenter)target.Parent;
  688. scroller.Offset = new Vector(0, 5);
  689. var scrolledContainers = containers
  690. .Skip(5)
  691. .Take(5)
  692. .Concat(containers.Take(5)).ToList();
  693. Assert.Equal(new Vector(0, 5), ((ILogicalScrollable)target).Offset);
  694. Assert.Equal(scrolledContainers, target.Panel.Children);
  695. for (var i = 0; i < target.Panel.Children.Count; ++i)
  696. {
  697. Assert.Equal(items[i + 5], target.Panel.Children[i].DataContext);
  698. }
  699. scroller.Offset = new Vector(0, 0);
  700. Assert.Equal(new Vector(0, 0), ((ILogicalScrollable)target).Offset);
  701. Assert.Equal(containers, target.Panel.Children);
  702. var dcs = target.Panel.Children.Select(x => x.DataContext).ToList();
  703. for (var i = 0; i < target.Panel.Children.Count; ++i)
  704. {
  705. Assert.Equal(items[i], target.Panel.Children[i].DataContext);
  706. }
  707. }
  708. [Fact]
  709. public void Scrolling_More_Than_A_Page_Should_Recycle_Items()
  710. {
  711. var target = CreateTarget(itemCount: 50);
  712. var items = (IList<string>)target.Items;
  713. target.ApplyTemplate();
  714. target.Measure(new Size(100, 100));
  715. target.Arrange(new Rect(0, 0, 100, 100));
  716. var containers = target.Panel.Children.ToList();
  717. var scroller = (ScrollContentPresenter)target.Parent;
  718. scroller.Offset = new Vector(0, 20);
  719. Assert.Equal(new Vector(0, 20), ((ILogicalScrollable)target).Offset);
  720. Assert.Equal(containers, target.Panel.Children);
  721. for (var i = 0; i < target.Panel.Children.Count; ++i)
  722. {
  723. Assert.Equal(items[i + 20], target.Panel.Children[i].DataContext);
  724. }
  725. scroller.Offset = new Vector(0, 0);
  726. Assert.Equal(new Vector(0, 0), ((ILogicalScrollable)target).Offset);
  727. Assert.Equal(containers, target.Panel.Children);
  728. for (var i = 0; i < target.Panel.Children.Count; ++i)
  729. {
  730. Assert.Equal(items[i], target.Panel.Children[i].DataContext);
  731. }
  732. }
  733. }
  734. private static ItemsPresenter CreateTarget(
  735. Orientation orientation = Orientation.Vertical,
  736. bool useContainers = true,
  737. int itemCount = 20,
  738. bool useAvaloniaList = false)
  739. {
  740. ItemsPresenter result;
  741. var itemsSource = Enumerable.Range(0, itemCount).Select(x => $"Item {x}");
  742. var items = useAvaloniaList ?
  743. (IEnumerable)new AvaloniaList<string>(itemsSource) :
  744. (IEnumerable)new ObservableCollection<string>(itemsSource);
  745. var scroller = new TestScroller
  746. {
  747. Content = result = new TestItemsPresenter(useContainers)
  748. {
  749. Items = items,
  750. ItemsPanel = VirtualizingPanelTemplate(orientation),
  751. ItemTemplate = ItemTemplate(),
  752. VirtualizationMode = ItemVirtualizationMode.Simple,
  753. }
  754. };
  755. scroller.UpdateChild();
  756. return result;
  757. }
  758. private static IDataTemplate ItemTemplate()
  759. {
  760. return new FuncDataTemplate<string>(x => new Canvas
  761. {
  762. Width = 10,
  763. Height = 10,
  764. });
  765. }
  766. private static ITemplate<IPanel> VirtualizingPanelTemplate(
  767. Orientation orientation = Orientation.Vertical)
  768. {
  769. return new FuncTemplate<IPanel>(() => new VirtualizingStackPanel
  770. {
  771. Orientation = orientation,
  772. });
  773. }
  774. private class TestScroller : ScrollContentPresenter, IRenderRoot
  775. {
  776. public IRenderer Renderer { get; }
  777. public Size ClientSize { get; }
  778. public IRenderTarget CreateRenderTarget()
  779. {
  780. throw new NotImplementedException();
  781. }
  782. public void Invalidate(Rect rect)
  783. {
  784. throw new NotImplementedException();
  785. }
  786. public Point PointToClient(Point point)
  787. {
  788. throw new NotImplementedException();
  789. }
  790. public Point PointToScreen(Point point)
  791. {
  792. throw new NotImplementedException();
  793. }
  794. }
  795. private class TestItemsPresenter : ItemsPresenter
  796. {
  797. private bool _useContainers;
  798. public TestItemsPresenter(bool useContainers)
  799. {
  800. _useContainers = useContainers;
  801. }
  802. protected override IItemContainerGenerator CreateItemContainerGenerator()
  803. {
  804. return _useContainers ?
  805. new ItemContainerGenerator<TestContainer>(this, TestContainer.ContentProperty, null) :
  806. new ItemContainerGenerator(this);
  807. }
  808. }
  809. private class TestContainer : ContentControl
  810. {
  811. public TestContainer()
  812. {
  813. Width = 10;
  814. Height = 10;
  815. }
  816. }
  817. }
  818. }