LayoutManagerTests.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using Avalonia.Controls;
  4. using Avalonia.Layout;
  5. using Avalonia.Threading;
  6. using Xunit;
  7. namespace Avalonia.Base.UnitTests.Layout
  8. {
  9. public class LayoutManagerTests
  10. {
  11. [Fact]
  12. public void Measures_And_Arranges_InvalidateMeasured_Control()
  13. {
  14. var control = new LayoutTestControl();
  15. var root = new LayoutTestRoot { Child = control };
  16. root.LayoutManager.ExecuteInitialLayoutPass();
  17. control.Measured = control.Arranged = false;
  18. control.InvalidateMeasure();
  19. root.LayoutManager.ExecuteLayoutPass();
  20. Assert.True(control.Measured);
  21. Assert.True(control.Arranged);
  22. }
  23. [Fact]
  24. public void Doesnt_Measure_And_Arrange_InvalidateMeasured_Control_When_TopLevel_Is_Not_Visible()
  25. {
  26. var control = new LayoutTestControl();
  27. var root = new LayoutTestRoot { Child = control, IsVisible = false };
  28. root.LayoutManager.ExecuteInitialLayoutPass();
  29. control.Measured = control.Arranged = false;
  30. control.InvalidateMeasure();
  31. root.LayoutManager.ExecuteLayoutPass();
  32. Assert.False(control.Measured);
  33. Assert.False(control.Arranged);
  34. }
  35. [Fact]
  36. public void Arranges_InvalidateArranged_Control()
  37. {
  38. var control = new LayoutTestControl();
  39. var root = new LayoutTestRoot { Child = control };
  40. root.LayoutManager.ExecuteInitialLayoutPass();
  41. control.Measured = control.Arranged = false;
  42. control.InvalidateArrange();
  43. root.LayoutManager.ExecuteLayoutPass();
  44. Assert.False(control.Measured);
  45. Assert.True(control.Arranged);
  46. }
  47. [Fact]
  48. public void Measures_Parent_Of_Newly_Added_Control()
  49. {
  50. var control = new LayoutTestControl();
  51. var root = new LayoutTestRoot();
  52. root.LayoutManager.ExecuteInitialLayoutPass();
  53. root.Child = control;
  54. root.Measured = root.Arranged = false;
  55. root.LayoutManager.ExecuteLayoutPass();
  56. Assert.True(root.Measured);
  57. Assert.True(root.Arranged);
  58. Assert.True(control.Measured);
  59. Assert.True(control.Arranged);
  60. }
  61. [Fact]
  62. public void Measures_In_Correct_Order()
  63. {
  64. LayoutTestControl control1;
  65. LayoutTestControl control2;
  66. var root = new LayoutTestRoot
  67. {
  68. Child = control1 = new LayoutTestControl
  69. {
  70. Child = control2 = new LayoutTestControl(),
  71. }
  72. };
  73. var order = new List<Layoutable>();
  74. Size MeasureOverride(Layoutable control, Size size)
  75. {
  76. order.Add(control);
  77. return new Size(10, 10);
  78. }
  79. root.DoMeasureOverride = MeasureOverride;
  80. control1.DoMeasureOverride = MeasureOverride;
  81. control2.DoMeasureOverride = MeasureOverride;
  82. root.LayoutManager.ExecuteInitialLayoutPass();
  83. control2.InvalidateMeasure();
  84. control1.InvalidateMeasure();
  85. root.InvalidateMeasure();
  86. order.Clear();
  87. root.LayoutManager.ExecuteLayoutPass();
  88. Assert.Equal(new Layoutable[] { root, control1, control2 }, order);
  89. }
  90. [Fact]
  91. public void Measures_Root_And_Grandparent_In_Correct_Order()
  92. {
  93. LayoutTestControl control1;
  94. LayoutTestControl control2;
  95. var root = new LayoutTestRoot
  96. {
  97. Child = control1 = new LayoutTestControl
  98. {
  99. Child = control2 = new LayoutTestControl(),
  100. }
  101. };
  102. var order = new List<Layoutable>();
  103. Size MeasureOverride(Layoutable control, Size size)
  104. {
  105. order.Add(control);
  106. return new Size(10, 10);
  107. }
  108. root.DoMeasureOverride = MeasureOverride;
  109. control1.DoMeasureOverride = MeasureOverride;
  110. control2.DoMeasureOverride = MeasureOverride;
  111. root.LayoutManager.ExecuteInitialLayoutPass();
  112. control2.InvalidateMeasure();
  113. root.InvalidateMeasure();
  114. order.Clear();
  115. root.LayoutManager.ExecuteLayoutPass();
  116. Assert.Equal(new Layoutable[] { root, control2 }, order);
  117. }
  118. [Fact]
  119. public void Doesnt_Measure_Non_Invalidated_Root()
  120. {
  121. var control = new LayoutTestControl();
  122. var root = new LayoutTestRoot { Child = control };
  123. root.LayoutManager.ExecuteInitialLayoutPass();
  124. root.Measured = root.Arranged = false;
  125. control.Measured = control.Arranged = false;
  126. control.InvalidateMeasure();
  127. root.LayoutManager.ExecuteLayoutPass();
  128. Assert.False(root.Measured);
  129. Assert.False(root.Arranged);
  130. Assert.True(control.Measured);
  131. Assert.True(control.Arranged);
  132. }
  133. [Fact]
  134. public void Doesnt_Measure_Removed_Control()
  135. {
  136. var control = new LayoutTestControl();
  137. var root = new LayoutTestRoot { Child = control };
  138. root.LayoutManager.ExecuteInitialLayoutPass();
  139. control.Measured = control.Arranged = false;
  140. control.InvalidateMeasure();
  141. root.Child = null;
  142. root.LayoutManager.ExecuteLayoutPass();
  143. Assert.False(control.Measured);
  144. Assert.False(control.Arranged);
  145. }
  146. [Fact]
  147. public void Measures_Root_With_Infinity()
  148. {
  149. var root = new LayoutTestRoot();
  150. var availableSize = default(Size);
  151. // Should not measure with this size.
  152. root.MaxClientSize = new Size(123, 456);
  153. root.DoMeasureOverride = (_, s) =>
  154. {
  155. availableSize = s;
  156. return new Size(100, 100);
  157. };
  158. root.LayoutManager.ExecuteInitialLayoutPass();
  159. Assert.Equal(Size.Infinity, availableSize);
  160. }
  161. [Fact]
  162. public void Arranges_Root_With_DesiredSize()
  163. {
  164. var root = new LayoutTestRoot
  165. {
  166. Width = 100,
  167. Height = 100,
  168. };
  169. var arrangeSize = default(Size);
  170. root.DoArrangeOverride = (_, s) =>
  171. {
  172. arrangeSize = s;
  173. return s;
  174. };
  175. root.LayoutManager.ExecuteInitialLayoutPass();
  176. Assert.Equal(new Size(100, 100), arrangeSize);
  177. root.Width = 120;
  178. root.LayoutManager.ExecuteLayoutPass();
  179. Assert.Equal(new Size(120, 100), arrangeSize);
  180. }
  181. [Fact]
  182. public void Invalidating_Child_Remeasures_Parent()
  183. {
  184. Border border;
  185. StackPanel panel;
  186. var root = new LayoutTestRoot
  187. {
  188. Child = panel = new StackPanel
  189. {
  190. Children =
  191. {
  192. (border = new Border())
  193. }
  194. }
  195. };
  196. root.LayoutManager.ExecuteInitialLayoutPass();
  197. Assert.Equal(new Size(0, 0), root.DesiredSize);
  198. border.Width = 100;
  199. border.Height = 100;
  200. root.LayoutManager.ExecuteLayoutPass();
  201. Assert.Equal(new Size(100, 100), panel.DesiredSize);
  202. }
  203. [Fact]
  204. public void LayoutManager_Should_Prevent_Infinite_Loop_On_Measure()
  205. {
  206. var control = new LayoutTestControl();
  207. var root = new LayoutTestRoot { Child = control };
  208. root.LayoutManager.ExecuteInitialLayoutPass();
  209. control.Measured = false;
  210. int cnt = 0;
  211. int maxcnt = 100;
  212. control.DoMeasureOverride = (l, s) =>
  213. {
  214. //emulate a problem in the logic of a control that triggers
  215. //invalidate measure during measure
  216. //it can lead to an infinite loop in layoutmanager
  217. if (++cnt < maxcnt)
  218. {
  219. control.InvalidateMeasure();
  220. }
  221. return new Size(100, 100);
  222. };
  223. control.InvalidateMeasure();
  224. root.LayoutManager.ExecuteLayoutPass();
  225. Assert.True(cnt < 100);
  226. }
  227. [Fact]
  228. public void LayoutManager_Should_Prevent_Infinite_Loop_On_Arrange()
  229. {
  230. var control = new LayoutTestControl();
  231. var root = new LayoutTestRoot { Child = control };
  232. root.LayoutManager.ExecuteInitialLayoutPass();
  233. control.Arranged = false;
  234. int cnt = 0;
  235. int maxcnt = 100;
  236. control.DoArrangeOverride = (l, s) =>
  237. {
  238. //emulate a problem in the logic of a control that triggers
  239. //invalidate measure during arrange
  240. //it can lead to infinity loop in layoutmanager
  241. if (++cnt < maxcnt)
  242. {
  243. control.InvalidateArrange();
  244. }
  245. return new Size(100, 100);
  246. };
  247. control.InvalidateArrange();
  248. root.LayoutManager.ExecuteLayoutPass();
  249. Assert.True(cnt < 100);
  250. }
  251. [Fact]
  252. public void LayoutManager_Should_Properly_Arrange_Visuals_Even_When_There_Are_Issues_With_Previous_Arranged()
  253. {
  254. var nonArrageableTargets = Enumerable.Range(1, 10).Select(_ => new LayoutTestControl()).ToArray();
  255. var targets = Enumerable.Range(1, 10).Select(_ => new LayoutTestControl()).ToArray();
  256. StackPanel panel;
  257. var root = new LayoutTestRoot
  258. {
  259. Child = panel = new StackPanel()
  260. };
  261. panel.Children.AddRange(nonArrageableTargets);
  262. panel.Children.AddRange(targets);
  263. root.LayoutManager.ExecuteInitialLayoutPass();
  264. foreach (var c in panel.Children.OfType<LayoutTestControl>())
  265. {
  266. c.Measured = c.Arranged = false;
  267. c.InvalidateMeasure();
  268. }
  269. foreach (var c in nonArrageableTargets)
  270. {
  271. c.DoArrangeOverride = (l, s) =>
  272. {
  273. //emulate a problem in the logic of a control that triggers
  274. //invalidate measure during arrange
  275. c.InvalidateMeasure();
  276. return new Size(100, 100);
  277. };
  278. }
  279. root.LayoutManager.ExecuteLayoutPass();
  280. //altough nonArrageableTargets has rubbish logic and can't be measured/arranged properly
  281. //layoutmanager should process properly other visuals
  282. Assert.All(targets, c => Assert.True(c.Arranged));
  283. }
  284. [Fact]
  285. public void LayoutManager_Should_Recover_From_Infinite_Loop_On_Measure()
  286. {
  287. // Test for issue #3041.
  288. var control = new LayoutTestControl();
  289. var root = new LayoutTestRoot { Child = control };
  290. root.LayoutManager.ExecuteInitialLayoutPass();
  291. control.Measured = false;
  292. control.DoMeasureOverride = (l, s) =>
  293. {
  294. control.InvalidateMeasure();
  295. return new Size(100, 100);
  296. };
  297. control.InvalidateMeasure();
  298. root.LayoutManager.ExecuteLayoutPass();
  299. // This is the important part: running a second layout pass in which we exceed the maximum
  300. // retries causes LayoutQueue<T>.Info.Count to exceed _maxEnqueueCountPerLoop.
  301. root.LayoutManager.ExecuteLayoutPass();
  302. control.Measured = false;
  303. control.DoMeasureOverride = null;
  304. root.LayoutManager.ExecuteLayoutPass();
  305. Assert.True(control.Measured);
  306. Assert.True(control.IsMeasureValid);
  307. }
  308. [Fact]
  309. public void Calling_ExecuteLayoutPass_From_ExecuteInitialLayoutPass_Does_Not_Break_Measure()
  310. {
  311. // Test for issue #3550.
  312. var control = new LayoutTestControl();
  313. var root = new LayoutTestRoot { Child = control };
  314. var count = 0;
  315. root.LayoutManager.ExecuteInitialLayoutPass();
  316. control.Measured = false;
  317. control.DoMeasureOverride = (l, s) =>
  318. {
  319. if (count++ == 0)
  320. {
  321. control.InvalidateMeasure();
  322. root.LayoutManager.ExecuteLayoutPass();
  323. return new Size(100, 100);
  324. }
  325. else
  326. {
  327. return new Size(200, 200);
  328. }
  329. };
  330. root.InvalidateMeasure();
  331. control.InvalidateMeasure();
  332. root.LayoutManager.ExecuteInitialLayoutPass();
  333. Assert.Equal(new Size(200, 200), control.Bounds.Size);
  334. Assert.Equal(new Size(200, 200), control.DesiredSize);
  335. }
  336. [Fact]
  337. public void LayoutManager_Execute_Layout_Pass_Should_Clear_Queued_LayoutPasses()
  338. {
  339. var control = new LayoutTestControl();
  340. var root = new LayoutTestRoot { Child = control };
  341. int layoutCount = 0;
  342. root.LayoutUpdated += (_, _) => layoutCount++;
  343. root.LayoutManager.InvalidateArrange(control);
  344. root.LayoutManager.ExecuteInitialLayoutPass();
  345. Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
  346. Assert.Equal(1, layoutCount);
  347. }
  348. }
  349. }