LayoutableTests.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. using System;
  2. using System.Collections.Generic;
  3. using Avalonia.Controls;
  4. using Avalonia.Layout;
  5. using Avalonia.UnitTests;
  6. using Moq;
  7. using Xunit;
  8. namespace Avalonia.Base.UnitTests.Layout
  9. {
  10. public class LayoutableTests
  11. {
  12. [Theory]
  13. [InlineData(0, 0, 0, 0, 100, 100)]
  14. [InlineData(10, 0, 0, 0, 90, 100)]
  15. [InlineData(10, 0, 5, 0, 85, 100)]
  16. [InlineData(0, 10, 0, 0, 100, 90)]
  17. [InlineData(0, 10, 0, 5, 100, 85)]
  18. [InlineData(4, 4, 6, 7, 90, 89)]
  19. public void Margin_Is_Applied_To_MeasureOverride_Size(
  20. double l,
  21. double t,
  22. double r,
  23. double b,
  24. double expectedWidth,
  25. double expectedHeight)
  26. {
  27. var target = new TestLayoutable
  28. {
  29. Margin = new Thickness(l, t, r, b),
  30. };
  31. target.Measure(new Size(100, 100));
  32. Assert.Equal(new Size(expectedWidth, expectedHeight), target.MeasureSize);
  33. }
  34. [Theory]
  35. [InlineData(HorizontalAlignment.Stretch, 100)]
  36. [InlineData(HorizontalAlignment.Left, 10)]
  37. [InlineData(HorizontalAlignment.Center, 10)]
  38. [InlineData(HorizontalAlignment.Right, 10)]
  39. public void HorizontalAlignment_Is_Applied_To_ArrangeOverride_Size(
  40. HorizontalAlignment h,
  41. double expectedWidth)
  42. {
  43. var target = new TestLayoutable
  44. {
  45. HorizontalAlignment = h,
  46. };
  47. target.Measure(Size.Infinity);
  48. target.Arrange(new Rect(0, 0, 100, 100));
  49. Assert.Equal(new Size(expectedWidth, 100), target.ArrangeSize);
  50. }
  51. [Theory]
  52. [InlineData(VerticalAlignment.Stretch, 100)]
  53. [InlineData(VerticalAlignment.Top, 10)]
  54. [InlineData(VerticalAlignment.Center, 10)]
  55. [InlineData(VerticalAlignment.Bottom, 10)]
  56. public void VerticalAlignment_Is_Applied_To_ArrangeOverride_Size(
  57. VerticalAlignment v,
  58. double expectedHeight)
  59. {
  60. var target = new TestLayoutable
  61. {
  62. VerticalAlignment = v,
  63. };
  64. target.Measure(Size.Infinity);
  65. target.Arrange(new Rect(0, 0, 100, 100));
  66. Assert.Equal(new Size(100, expectedHeight), target.ArrangeSize);
  67. }
  68. [Theory]
  69. [InlineData(0, 0, 0, 0, 100, 100)]
  70. [InlineData(10, 0, 0, 0, 90, 100)]
  71. [InlineData(10, 0, 5, 0, 85, 100)]
  72. [InlineData(0, 10, 0, 0, 100, 90)]
  73. [InlineData(0, 10, 0, 5, 100, 85)]
  74. [InlineData(4, 4, 6, 7, 90, 89)]
  75. public void Margin_Is_Applied_To_ArrangeOverride_Size(
  76. double l,
  77. double t,
  78. double r,
  79. double b,
  80. double expectedWidth,
  81. double expectedHeight)
  82. {
  83. var target = new TestLayoutable
  84. {
  85. Margin = new Thickness(l, t, r, b),
  86. };
  87. target.Measure(Size.Infinity);
  88. target.Arrange(new Rect(0, 0, 100, 100));
  89. Assert.Equal(new Size(expectedWidth, expectedHeight), target.ArrangeSize);
  90. }
  91. [Fact]
  92. public void Only_Calls_LayoutManager_InvalidateMeasure_Once()
  93. {
  94. var target = new Mock<ILayoutManager>();
  95. var control = new Decorator();
  96. var root = new LayoutTestRoot
  97. {
  98. Child = control,
  99. LayoutManager = target.Object,
  100. };
  101. root.Measure(Size.Infinity);
  102. root.Arrange(new Rect(root.DesiredSize));
  103. target.Invocations.Clear();
  104. control.InvalidateMeasure();
  105. control.InvalidateMeasure();
  106. target.Verify(x => x.InvalidateMeasure(control), Times.Once());
  107. }
  108. [Fact]
  109. public void Only_Calls_LayoutManager_InvalidateArrange_Once()
  110. {
  111. var target = new Mock<ILayoutManager>();
  112. var control = new Decorator();
  113. var root = new LayoutTestRoot
  114. {
  115. Child = control,
  116. LayoutManager = target.Object,
  117. };
  118. root.Measure(Size.Infinity);
  119. root.Arrange(new Rect(root.DesiredSize));
  120. target.Invocations.Clear();
  121. control.InvalidateArrange();
  122. control.InvalidateArrange();
  123. target.Verify(x => x.InvalidateArrange(control), Times.Once());
  124. }
  125. [Fact]
  126. public void Attaching_Control_To_Tree_Invalidates_Parent_Measure()
  127. {
  128. var target = new Mock<ILayoutManager>();
  129. var control = new Decorator();
  130. var root = new LayoutTestRoot
  131. {
  132. Child = control,
  133. LayoutManager = target.Object,
  134. };
  135. root.Measure(Size.Infinity);
  136. root.Arrange(new Rect(root.DesiredSize));
  137. Assert.True(control.IsMeasureValid);
  138. root.Child = null;
  139. root.Measure(Size.Infinity);
  140. root.Arrange(new Rect(root.DesiredSize));
  141. Assert.False(control.IsMeasureValid);
  142. Assert.True(root.IsMeasureValid);
  143. target.Invocations.Clear();
  144. root.Child = control;
  145. Assert.False(root.IsMeasureValid);
  146. Assert.False(control.IsMeasureValid);
  147. target.Verify(x => x.InvalidateMeasure(root), Times.Once());
  148. }
  149. [Fact]
  150. public void LayoutUpdated_Is_Called_At_End_Of_Layout_Pass()
  151. {
  152. Border border1;
  153. Border border2;
  154. var root = new TestRoot
  155. {
  156. Child = border1 = new Border
  157. {
  158. Child = border2 = new Border(),
  159. },
  160. };
  161. var raised = 0;
  162. void ValidateBounds(object sender, EventArgs e)
  163. {
  164. Assert.Equal(new Rect(0, 0, 100, 100), border1.Bounds);
  165. Assert.Equal(new Rect(0, 0, 100, 100), border2.Bounds);
  166. ++raised;
  167. }
  168. root.LayoutUpdated += ValidateBounds;
  169. border1.LayoutUpdated += ValidateBounds;
  170. border2.LayoutUpdated += ValidateBounds;
  171. root.Measure(new Size(100, 100));
  172. root.Arrange(new Rect(0, 0, 100, 100));
  173. root.LayoutManager.ExecuteLayoutPass();
  174. Assert.Equal(3, raised);
  175. Assert.Equal(new Rect(0, 0, 100, 100), border1.Bounds);
  176. Assert.Equal(new Rect(0, 0, 100, 100), border2.Bounds);
  177. }
  178. [Fact]
  179. public void LayoutUpdated_Subscribes_To_LayoutManager()
  180. {
  181. Border target;
  182. var layoutManager = new Mock<ILayoutManager>();
  183. layoutManager.SetupAdd(m => m.LayoutUpdated += (sender, args) => { });
  184. var root = new TestRoot
  185. {
  186. Child = new Border
  187. {
  188. Child = target = new Border(),
  189. },
  190. LayoutManager = layoutManager.Object,
  191. };
  192. void Handler(object sender, EventArgs e) {}
  193. layoutManager.Invocations.Clear();
  194. target.LayoutUpdated += Handler;
  195. layoutManager.VerifyAdd(
  196. x => x.LayoutUpdated += It.IsAny<EventHandler>(),
  197. Times.Once);
  198. layoutManager.Invocations.Clear();
  199. target.LayoutUpdated -= Handler;
  200. layoutManager.VerifyRemove(
  201. x => x.LayoutUpdated -= It.IsAny<EventHandler>(),
  202. Times.Once);
  203. }
  204. [Fact]
  205. public void LayoutManager_LayoutUpdated_Is_Subscribed_When_Attached_To_Tree()
  206. {
  207. Border border1;
  208. var layoutManager = new Mock<ILayoutManager>();
  209. layoutManager.SetupAdd(m => m.LayoutUpdated += (sender, args) => { });
  210. var root = new TestRoot
  211. {
  212. Child = border1 = new Border(),
  213. LayoutManager = layoutManager.Object,
  214. };
  215. var border2 = new Border();
  216. border2.LayoutUpdated += (s, e) => { };
  217. layoutManager.Invocations.Clear();
  218. border1.Child = border2;
  219. layoutManager.VerifyAdd(
  220. x => x.LayoutUpdated += It.IsAny<EventHandler>(),
  221. Times.Once);
  222. }
  223. [Fact]
  224. public void LayoutManager_LayoutUpdated_Is_Unsubscribed_When_Detached_From_Tree()
  225. {
  226. Border border1;
  227. var layoutManager = new Mock<ILayoutManager>();
  228. layoutManager.SetupAdd(m => m.LayoutUpdated += (sender, args) => { });
  229. var root = new TestRoot
  230. {
  231. Child = border1 = new Border(),
  232. LayoutManager = layoutManager.Object,
  233. };
  234. var border2 = new Border();
  235. border2.LayoutUpdated += (s, e) => { };
  236. border1.Child = border2;
  237. layoutManager.Invocations.Clear();
  238. border1.Child = null;
  239. layoutManager.VerifyRemove(
  240. x => x.LayoutUpdated -= It.IsAny<EventHandler>(),
  241. Times.Once);
  242. }
  243. [Fact]
  244. public void LayoutManager_LayoutUpdated_Should_Not_Be_Subscribed_Twice_In_AttachedToVisualTree()
  245. {
  246. Border border1;
  247. var layoutManager = new Mock<ILayoutManager>();
  248. layoutManager.SetupAdd(m => m.LayoutUpdated += (_, _) => { });
  249. _ = new TestRoot
  250. {
  251. Child = border1 = new Border(),
  252. LayoutManager = layoutManager.Object,
  253. };
  254. var border2 = new Border();
  255. border2.AttachedToVisualTree += (_, _) => border2.LayoutUpdated += (_, _) => { };
  256. layoutManager.Invocations.Clear();
  257. border1.Child = border2;
  258. layoutManager.VerifyAdd(
  259. x => x.LayoutUpdated += It.IsAny<EventHandler>(),
  260. Times.Once);
  261. }
  262. [Fact]
  263. public void Making_Control_Invisible_Should_Invalidate_Parent_Measure()
  264. {
  265. Border child;
  266. var target = new StackPanel
  267. {
  268. Children =
  269. {
  270. (child = new Border
  271. {
  272. Width = 100,
  273. }),
  274. }
  275. };
  276. target.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  277. target.Arrange(new Rect(target.DesiredSize));
  278. Assert.True(target.IsMeasureValid);
  279. Assert.True(target.IsArrangeValid);
  280. Assert.True(child.IsMeasureValid);
  281. Assert.True(child.IsArrangeValid);
  282. child.IsVisible = false;
  283. Assert.False(target.IsMeasureValid);
  284. Assert.False(target.IsArrangeValid);
  285. Assert.True(child.IsMeasureValid);
  286. Assert.True(child.IsArrangeValid);
  287. }
  288. [Fact]
  289. public void Making_Control_Visible_Should_Invalidate_Own_And_Parent_Measure()
  290. {
  291. Border child;
  292. var target = new StackPanel
  293. {
  294. Children =
  295. {
  296. (child = new Border
  297. {
  298. Width = 100,
  299. IsVisible = false,
  300. }),
  301. }
  302. };
  303. target.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  304. target.Arrange(new Rect(target.DesiredSize));
  305. Assert.True(target.IsMeasureValid);
  306. Assert.True(target.IsArrangeValid);
  307. Assert.True(child.IsMeasureValid);
  308. Assert.False(child.IsArrangeValid);
  309. child.IsVisible = true;
  310. Assert.False(target.IsMeasureValid);
  311. Assert.False(target.IsArrangeValid);
  312. Assert.False(child.IsMeasureValid);
  313. Assert.False(child.IsArrangeValid);
  314. }
  315. [Fact]
  316. public void Measuring_Invisible_Control_Should_Not_Invalidate_Parent_Measure()
  317. {
  318. Border child;
  319. var target = new StackPanel
  320. {
  321. Children =
  322. {
  323. (child = new Border
  324. {
  325. Width = 100,
  326. }),
  327. }
  328. };
  329. target.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  330. target.Arrange(new Rect(target.DesiredSize));
  331. Assert.True(target.IsMeasureValid);
  332. Assert.True(target.IsArrangeValid);
  333. Assert.Equal(new Size(100, 0), child.DesiredSize);
  334. child.IsVisible = false;
  335. Assert.Equal(default, child.DesiredSize);
  336. target.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  337. target.Arrange(new Rect(target.DesiredSize));
  338. child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  339. Assert.True(target.IsMeasureValid);
  340. Assert.True(target.IsArrangeValid);
  341. Assert.Equal(default, child.DesiredSize);
  342. }
  343. [Fact]
  344. public void Size_Properties_Reject_Invalid_Values()
  345. {
  346. var target = new Layoutable();
  347. Assert.Multiple(() =>
  348. {
  349. SetShouldThrow([Layoutable.WidthProperty, Layoutable.HeightProperty], double.PositiveInfinity);
  350. SetShouldThrow([Layoutable.WidthProperty, Layoutable.HeightProperty], -10);
  351. SetShouldThrow([Layoutable.MinWidthProperty, Layoutable.MinHeightProperty], double.PositiveInfinity);
  352. SetShouldThrow([Layoutable.MinWidthProperty, Layoutable.MinHeightProperty], -10);
  353. SetShouldThrow([Layoutable.MaxWidthProperty, Layoutable.MaxHeightProperty], -10);
  354. void SetShouldThrow(IEnumerable<StyledProperty<double>> properies, double value)
  355. {
  356. foreach (var prop in properies)
  357. Assert.Throws<ArgumentException>(() => target.SetValue(prop, value));
  358. }
  359. });
  360. }
  361. [Fact]
  362. public void Constraint_And_Negative_Margin()
  363. {
  364. using var app = UnitTestApplication.Start(TestServices.MockPlatformRenderInterface);
  365. var textBlock = new TextBlock
  366. {
  367. Margin = new Thickness(-10),
  368. Text = "Lorem ipsum dolor sit amet",
  369. };
  370. var border = new Border
  371. {
  372. MaxWidth = 100,
  373. Child = textBlock,
  374. };
  375. border.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  376. border.Arrange(new Rect(default, border.DesiredSize));
  377. Assert.Multiple(() =>
  378. {
  379. Assert.Equal(new Size(100, 0), border.DesiredSize);
  380. Assert.Equal(new Rect(0, 0, 100, 0), border.Bounds);
  381. Assert.Equal(new Size(100, 0), textBlock.DesiredSize);
  382. Assert.Equal(new Rect(-10, -10, 120, 20), textBlock.Bounds);
  383. });
  384. }
  385. private class TestLayoutable : Layoutable
  386. {
  387. public Size ArrangeSize { get; private set; }
  388. public Size MeasureResult { get; set; } = new Size(10, 10);
  389. public Size MeasureSize { get; private set; }
  390. protected override Size MeasureOverride(Size availableSize)
  391. {
  392. MeasureSize = availableSize;
  393. return MeasureResult;
  394. }
  395. protected override Size ArrangeOverride(Size finalSize)
  396. {
  397. ArrangeSize = finalSize;
  398. return base.ArrangeOverride(finalSize);
  399. }
  400. }
  401. }
  402. }