LayoutTests.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. using Xunit.Abstractions;
  2. // Alias Console to MockConsole so we don't accidentally use Console
  3. namespace Terminal.Gui.ViewTests;
  4. public class LayoutTests
  5. {
  6. private readonly ITestOutputHelper _output;
  7. public LayoutTests (ITestOutputHelper output) { _output = output; }
  8. // Tested in AbsoluteLayoutTests.cs
  9. // public void Pos_Dim_Are_Null_If_Not_Initialized_On_Constructor_IsAdded_False ()
  10. [Theory]
  11. [AutoInitShutdown]
  12. [InlineData (1)]
  13. [InlineData (2)]
  14. [InlineData (3)]
  15. [InlineData (4)]
  16. [InlineData (5)]
  17. [InlineData (6)]
  18. [InlineData (7)]
  19. [InlineData (8)]
  20. [InlineData (9)]
  21. [InlineData (10)]
  22. public void Dim_CenteredSubView_85_Percent_Height (int height)
  23. {
  24. var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () };
  25. var subview = new Window
  26. {
  27. X = Pos.Center (), Y = Pos.Center (), Width = Dim.Percent (85), Height = Dim.Percent (85)
  28. };
  29. win.Add (subview);
  30. RunState rs = Application.Begin (win);
  31. var firstIteration = false;
  32. ((FakeDriver)Application.Driver).SetBufferSize (20, height);
  33. Application.RunIteration (ref rs, ref firstIteration);
  34. var expected = string.Empty;
  35. switch (height)
  36. {
  37. case 1:
  38. //Assert.Equal (new Rect (0, 0, 17, 0), subview.Frame);
  39. expected = @"
  40. ────────────────────";
  41. break;
  42. case 2:
  43. //Assert.Equal (new Rect (0, 0, 17, 1), subview.Frame);
  44. expected = @"
  45. ┌──────────────────┐
  46. └──────────────────┘
  47. ";
  48. break;
  49. case 3:
  50. //Assert.Equal (new Rect (0, 0, 17, 2), subview.Frame);
  51. expected = @"
  52. ┌──────────────────┐
  53. │ │
  54. └──────────────────┘
  55. ";
  56. break;
  57. case 4:
  58. //Assert.Equal (new Rect (0, 0, 17, 3), subview.Frame);
  59. expected = @"
  60. ┌──────────────────┐
  61. │ ─────────────── │
  62. │ │
  63. └──────────────────┘";
  64. break;
  65. case 5:
  66. //Assert.Equal (new Rect (0, 0, 17, 3), subview.Frame);
  67. expected = @"
  68. ┌──────────────────┐
  69. │ ┌─────────────┐ │
  70. │ └─────────────┘ │
  71. │ │
  72. └──────────────────┘";
  73. break;
  74. case 6:
  75. //Assert.Equal (new Rect (0, 0, 17, 3), subview.Frame);
  76. expected = @"
  77. ┌──────────────────┐
  78. │ ┌─────────────┐ │
  79. │ │ │ │
  80. │ └─────────────┘ │
  81. │ │
  82. └──────────────────┘";
  83. break;
  84. case 7:
  85. //Assert.Equal (new Rect (0, 0, 17, 3), subview.Frame);
  86. expected = @"
  87. ┌──────────────────┐
  88. │ ┌─────────────┐ │
  89. │ │ │ │
  90. │ │ │ │
  91. │ └─────────────┘ │
  92. │ │
  93. └──────────────────┘";
  94. break;
  95. case 8:
  96. //Assert.Equal (new Rect (0, 0, 17, 3), subview.Frame);
  97. expected = @"
  98. ┌──────────────────┐
  99. │ ┌─────────────┐ │
  100. │ │ │ │
  101. │ │ │ │
  102. │ │ │ │
  103. │ └─────────────┘ │
  104. │ │
  105. └──────────────────┘";
  106. break;
  107. case 9:
  108. //Assert.Equal (new Rect (0, 0, 17, 3), subview.Frame);
  109. expected = @"
  110. ┌──────────────────┐
  111. │ │
  112. │ ┌─────────────┐ │
  113. │ │ │ │
  114. │ │ │ │
  115. │ │ │ │
  116. │ └─────────────┘ │
  117. │ │
  118. └──────────────────┘";
  119. break;
  120. case 10:
  121. //Assert.Equal (new Rect (0, 0, 17, 3), subview.Frame);
  122. expected = @"
  123. ┌──────────────────┐
  124. │ │
  125. │ ┌─────────────┐ │
  126. │ │ │ │
  127. │ │ │ │
  128. │ │ │ │
  129. │ │ │ │
  130. │ └─────────────┘ │
  131. │ │
  132. └──────────────────┘";
  133. break;
  134. }
  135. _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
  136. Application.End (rs);
  137. }
  138. [Theory]
  139. [AutoInitShutdown]
  140. [InlineData (1)]
  141. [InlineData (2)]
  142. [InlineData (3)]
  143. [InlineData (4)]
  144. [InlineData (5)]
  145. [InlineData (6)]
  146. [InlineData (7)]
  147. [InlineData (8)]
  148. [InlineData (9)]
  149. [InlineData (10)]
  150. public void Dim_CenteredSubView_85_Percent_Width (int width)
  151. {
  152. var win = new Window { Width = Dim.Fill (), Height = Dim.Fill () };
  153. var subview = new Window
  154. {
  155. X = Pos.Center (), Y = Pos.Center (), Width = Dim.Percent (85), Height = Dim.Percent (85)
  156. };
  157. win.Add (subview);
  158. RunState rs = Application.Begin (win);
  159. var firstIteration = false;
  160. ((FakeDriver)Application.Driver).SetBufferSize (width, 7);
  161. Application.RunIteration (ref rs, ref firstIteration);
  162. var expected = string.Empty;
  163. switch (width)
  164. {
  165. case 1:
  166. Assert.Equal (new Rectangle (0, 0, 0, 4), subview.Frame);
  167. expected = @"
  168. │";
  169. break;
  170. case 2:
  171. Assert.Equal (new Rectangle (0, 0, 0, 4), subview.Frame);
  172. expected = @"
  173. ┌┐
  174. ││
  175. ││
  176. ││
  177. ││
  178. ││
  179. └┘";
  180. break;
  181. case 3:
  182. Assert.Equal (new Rectangle (0, 0, 0, 4), subview.Frame);
  183. expected = @"
  184. ┌─┐
  185. │ │
  186. │ │
  187. │ │
  188. │ │
  189. │ │
  190. └─┘";
  191. break;
  192. case 4:
  193. Assert.Equal (new Rectangle (0, 0, 1, 4), subview.Frame);
  194. expected = @"
  195. ┌──┐
  196. ││ │
  197. ││ │
  198. ││ │
  199. ││ │
  200. │ │
  201. └──┘";
  202. break;
  203. case 5:
  204. Assert.Equal (new Rectangle (0, 0, 2, 4), subview.Frame);
  205. expected = @"
  206. ┌───┐
  207. │┌┐ │
  208. │││ │
  209. │││ │
  210. │└┘ │
  211. │ │
  212. └───┘";
  213. break;
  214. case 6:
  215. Assert.Equal (new Rectangle (0, 0, 3, 4), subview.Frame);
  216. expected = @"
  217. ┌────┐
  218. │┌─┐ │
  219. ││ │ │
  220. ││ │ │
  221. │└─┘ │
  222. │ │
  223. └────┘";
  224. break;
  225. case 7:
  226. Assert.Equal (new Rectangle (0, 0, 4, 4), subview.Frame);
  227. expected = @"
  228. ┌─────┐
  229. │┌──┐ │
  230. ││ │ │
  231. ││ │ │
  232. │└──┘ │
  233. │ │
  234. └─────┘";
  235. break;
  236. case 8:
  237. Assert.Equal (new Rectangle (0, 0, 5, 4), subview.Frame);
  238. expected = @"
  239. ┌──────┐
  240. │┌───┐ │
  241. ││ │ │
  242. ││ │ │
  243. │└───┘ │
  244. │ │
  245. └──────┘";
  246. break;
  247. case 9:
  248. Assert.Equal (new Rectangle (1, 0, 5, 4), subview.Frame);
  249. expected = @"
  250. ┌───────┐
  251. │ ┌───┐ │
  252. │ │ │ │
  253. │ │ │ │
  254. │ └───┘ │
  255. │ │
  256. └───────┘";
  257. break;
  258. case 10:
  259. Assert.Equal (new Rectangle (1, 0, 6, 4), subview.Frame);
  260. expected = @"
  261. ┌────────┐
  262. │ ┌────┐ │
  263. │ │ │ │
  264. │ │ │ │
  265. │ └────┘ │
  266. │ │
  267. └────────┘";
  268. break;
  269. }
  270. _ = TestHelpers.AssertDriverContentsWithFrameAre (expected, _output);
  271. Application.End (rs);
  272. }
  273. [Fact]
  274. [AutoInitShutdown]
  275. public void DimFill_SizedCorrectly ()
  276. {
  277. var view = new View { Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Single };
  278. Application.Top.Add (view);
  279. RunState rs = Application.Begin (Application.Top);
  280. ((FakeDriver)Application.Driver).SetBufferSize (32, 5);
  281. //view.SetNeedsLayout ();
  282. Application.Top.LayoutSubviews ();
  283. //view.SetRelativeLayout (new Rect (0, 0, 32, 5));
  284. Assert.Equal (32, view.Frame.Width);
  285. Assert.Equal (5, view.Frame.Height);
  286. }
  287. [Fact]
  288. [TestRespondersDisposed]
  289. public void Draw_Throws_IndexOutOfRangeException_With_Negative_Bounds ()
  290. {
  291. Application.Init (new FakeDriver ());
  292. Toplevel top = Application.Top;
  293. var view = new View { X = -2, Text = "view" };
  294. top.Add (view);
  295. Application.Iteration += (s, a) =>
  296. {
  297. Assert.Equal (-2, view.X);
  298. Application.RequestStop ();
  299. };
  300. try
  301. {
  302. Application.Run ();
  303. }
  304. catch (IndexOutOfRangeException ex)
  305. {
  306. // After the fix this exception will not be caught.
  307. Assert.IsType<IndexOutOfRangeException> (ex);
  308. }
  309. // Shutdown must be called to safely clean up Application if Init has been called
  310. Application.Shutdown ();
  311. }
  312. [Fact]
  313. [TestRespondersDisposed]
  314. public void Draw_Vertical_Throws_IndexOutOfRangeException_With_Negative_Bounds ()
  315. {
  316. Application.Init (new FakeDriver ());
  317. Toplevel top = Application.Top;
  318. var view = new View { Y = -2, Height = 10, TextDirection = TextDirection.TopBottom_LeftRight, Text = "view" };
  319. top.Add (view);
  320. Application.Iteration += (s, a) =>
  321. {
  322. Assert.Equal (-2, view.Y);
  323. Application.RequestStop ();
  324. };
  325. try
  326. {
  327. Application.Run ();
  328. }
  329. catch (IndexOutOfRangeException ex)
  330. {
  331. // After the fix this exception will not be caught.
  332. Assert.IsType<IndexOutOfRangeException> (ex);
  333. }
  334. // Shutdown must be called to safely clean up Application if Init has been called
  335. Application.Shutdown ();
  336. }
  337. [Fact]
  338. public void LayoutSubviews_No_SuperView ()
  339. {
  340. var root = new View ();
  341. var first = new View
  342. {
  343. Id = "first",
  344. X = 1,
  345. Y = 2,
  346. Height = 3,
  347. Width = 4
  348. };
  349. root.Add (first);
  350. var second = new View { Id = "second" };
  351. root.Add (second);
  352. second.X = Pos.Right (first) + 1;
  353. root.LayoutSubviews ();
  354. Assert.Equal (6, second.Frame.X);
  355. root.Dispose ();
  356. first.Dispose ();
  357. second.Dispose ();
  358. }
  359. [Fact]
  360. public void LayoutSubviews_RootHas_SuperView ()
  361. {
  362. var top = new View ();
  363. var root = new View ();
  364. top.Add (root);
  365. var first = new View
  366. {
  367. Id = "first",
  368. X = 1,
  369. Y = 2,
  370. Height = 3,
  371. Width = 4
  372. };
  373. root.Add (first);
  374. var second = new View { Id = "second" };
  375. root.Add (second);
  376. second.X = Pos.Right (first) + 1;
  377. root.LayoutSubviews ();
  378. Assert.Equal (6, second.Frame.X);
  379. root.Dispose ();
  380. top.Dispose ();
  381. first.Dispose ();
  382. second.Dispose ();
  383. }
  384. [Fact]
  385. public void LayoutSubviews_ViewThatRefsSubView_Throws ()
  386. {
  387. var root = new View ();
  388. var super = new View ();
  389. root.Add (super);
  390. var sub = new View ();
  391. super.Add (sub);
  392. super.Width = Dim.Width (sub);
  393. Assert.Throws<InvalidOperationException> (() => root.LayoutSubviews ());
  394. root.Dispose ();
  395. super.Dispose ();
  396. }
  397. // Was named AutoSize_Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute ()
  398. // but doesn't actually have anything to do with AutoSize.
  399. [Fact]
  400. public void
  401. Pos_Validation_Do_Not_Throws_If_NewValue_Is_PosAbsolute_And_OldValue_Is_Another_Type_After_Sets_To_LayoutStyle_Absolute ()
  402. {
  403. Application.Init (new FakeDriver ());
  404. Toplevel t = Application.Top;
  405. var w = new Window { X = Pos.Left (t) + 2, Y = Pos.At (2) };
  406. var v = new View { X = Pos.Center (), Y = Pos.Percent (10) };
  407. w.Add (v);
  408. t.Add (w);
  409. t.Ready += (s, e) =>
  410. {
  411. v.Frame = new Rectangle (2, 2, 10, 10);
  412. Assert.Equal (2, v.X = 2);
  413. Assert.Equal (2, v.Y = 2);
  414. };
  415. Application.Iteration += (s, a) => Application.RequestStop ();
  416. Application.Run ();
  417. Application.Shutdown ();
  418. }
  419. [Fact]
  420. [AutoInitShutdown]
  421. public void PosCombine_DimCombine_View_With_SubViews ()
  422. {
  423. var clicked = false;
  424. Toplevel top = Application.Top;
  425. var win1 = new Window { Id = "win1", Width = 20, Height = 10 };
  426. var view1 = new View { Text = "view1", AutoSize = true }; // BUGBUG: AutoSize or Width must be set
  427. var win2 = new Window { Id = "win2", Y = Pos.Bottom (view1) + 1, Width = 10, Height = 3 };
  428. var view2 = new View { Id = "view2", Width = Dim.Fill (), Height = 1, CanFocus = true };
  429. view2.MouseClick += (sender, e) => clicked = true;
  430. var view3 = new View { Id = "view3", Width = Dim.Fill (1), Height = 1, CanFocus = true };
  431. view2.Add (view3);
  432. win2.Add (view2);
  433. win1.Add (view1, win2);
  434. top.Add (win1);
  435. RunState rs = Application.Begin (top);
  436. TestHelpers.AssertDriverContentsWithFrameAre (
  437. @"
  438. ┌──────────────────┐
  439. │view1 │
  440. │ │
  441. │┌────────┐ │
  442. ││ │ │
  443. │└────────┘ │
  444. │ │
  445. │ │
  446. │ │
  447. └──────────────────┘",
  448. _output
  449. );
  450. Assert.Equal (new Rectangle (0, 0, 80, 25), top.Frame);
  451. Assert.Equal (new Rectangle (0, 0, 5, 1), view1.Frame);
  452. Assert.Equal (new Rectangle (0, 0, 20, 10), win1.Frame);
  453. Assert.Equal (new Rectangle (0, 2, 10, 3), win2.Frame);
  454. Assert.Equal (new Rectangle (0, 0, 8, 1), view2.Frame);
  455. Assert.Equal (new Rectangle (0, 0, 7, 1), view3.Frame);
  456. var foundView = View.FindDeepestView (top, 9, 4, out int rx, out int ry);
  457. Assert.Equal (foundView, view2);
  458. Application.OnMouseEvent (
  459. new MouseEventEventArgs (
  460. new MouseEvent { X = 9, Y = 4, Flags = MouseFlags.Button1Clicked }
  461. )
  462. );
  463. Assert.True (clicked);
  464. Application.End (rs);
  465. }
  466. [Fact]
  467. public void PosCombine_Refs_SuperView_Throws ()
  468. {
  469. Application.Init (new FakeDriver ());
  470. var w = new Window { X = Pos.Left (Application.Top) + 2, Y = Pos.Top (Application.Top) + 2 };
  471. var f = new FrameView ();
  472. var v1 = new View { X = Pos.Left (w) + 2, Y = Pos.Top (w) + 2 };
  473. var v2 = new View { X = Pos.Left (v1) + 2, Y = Pos.Top (v1) + 2 };
  474. f.Add (v1, v2);
  475. w.Add (f);
  476. Application.Top.Add (w);
  477. f.X = Pos.X (Application.Top) + Pos.X (v2) - Pos.X (v1);
  478. f.Y = Pos.Y (Application.Top) + Pos.Y (v2) - Pos.Y (v1);
  479. Application.Top.LayoutComplete += (s, e) =>
  480. {
  481. Assert.Equal (0, Application.Top.Frame.X);
  482. Assert.Equal (0, Application.Top.Frame.Y);
  483. Assert.Equal (2, w.Frame.X);
  484. Assert.Equal (2, w.Frame.Y);
  485. Assert.Equal (2, f.Frame.X);
  486. Assert.Equal (2, f.Frame.Y);
  487. Assert.Equal (4, v1.Frame.X);
  488. Assert.Equal (4, v1.Frame.Y);
  489. Assert.Equal (6, v2.Frame.X);
  490. Assert.Equal (6, v2.Frame.Y);
  491. };
  492. Application.Iteration += (s, a) => Application.RequestStop ();
  493. Assert.Throws<InvalidOperationException> (() => Application.Run ());
  494. Application.Shutdown ();
  495. }
  496. [Fact]
  497. public void TopologicalSort_Missing_Add ()
  498. {
  499. var root = new View ();
  500. var sub1 = new View ();
  501. root.Add (sub1);
  502. var sub2 = new View ();
  503. sub1.Width = Dim.Width (sub2);
  504. Assert.Throws<InvalidOperationException> (() => root.LayoutSubviews ());
  505. sub2.Width = Dim.Width (sub1);
  506. Assert.Throws<InvalidOperationException> (() => root.LayoutSubviews ());
  507. root.Dispose ();
  508. sub1.Dispose ();
  509. sub2.Dispose ();
  510. }
  511. [Fact]
  512. public void TopologicalSort_Recursive_Ref ()
  513. {
  514. var root = new View ();
  515. var sub1 = new View ();
  516. root.Add (sub1);
  517. var sub2 = new View ();
  518. root.Add (sub2);
  519. sub2.Width = Dim.Width (sub2);
  520. Exception exception = Record.Exception (root.LayoutSubviews);
  521. Assert.Null (exception);
  522. root.Dispose ();
  523. sub1.Dispose ();
  524. sub2.Dispose ();
  525. }
  526. [Fact]
  527. [AutoInitShutdown]
  528. public void TrySetHeight_ForceValidatePosDim ()
  529. {
  530. var top = new View { X = 0, Y = 0, Height = 20 };
  531. var v = new View { Height = Dim.Fill (), ValidatePosDim = true };
  532. top.Add (v);
  533. Assert.False (v.TrySetHeight (10, out int rHeight));
  534. Assert.Equal (10, rHeight);
  535. v.Height = Dim.Fill (1);
  536. Assert.False (v.TrySetHeight (10, out rHeight));
  537. Assert.Equal (9, rHeight);
  538. v.Height = 0;
  539. Assert.True (v.TrySetHeight (10, out rHeight));
  540. Assert.Equal (10, rHeight);
  541. Assert.False (v.IsInitialized);
  542. Application.Top.Add (top);
  543. Application.Begin (Application.Top);
  544. Assert.True (v.IsInitialized);
  545. v.Height = 15;
  546. Assert.True (v.TrySetHeight (5, out rHeight));
  547. Assert.Equal (5, rHeight);
  548. }
  549. [Fact]
  550. [AutoInitShutdown]
  551. public void TrySetWidth_ForceValidatePosDim ()
  552. {
  553. var top = new View { X = 0, Y = 0, Width = 80 };
  554. var v = new View { Width = Dim.Fill (), ValidatePosDim = true };
  555. top.Add (v);
  556. Assert.False (v.TrySetWidth (70, out int rWidth));
  557. Assert.Equal (70, rWidth);
  558. v.Width = Dim.Fill (1);
  559. Assert.False (v.TrySetWidth (70, out rWidth));
  560. Assert.Equal (69, rWidth);
  561. v.Width = 0;
  562. Assert.True (v.TrySetWidth (70, out rWidth));
  563. Assert.Equal (70, rWidth);
  564. Assert.False (v.IsInitialized);
  565. Application.Top.Add (top);
  566. Application.Begin (Application.Top);
  567. Assert.True (v.IsInitialized);
  568. v.Width = 75;
  569. Assert.True (v.TrySetWidth (60, out rWidth));
  570. Assert.Equal (60, rWidth);
  571. }
  572. }