ViewDrawingClippingTests.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  1. #nullable enable
  2. using UnitTests;
  3. using Xunit.Abstractions;
  4. namespace UnitTests_Parallelizable.ViewTests;
  5. public class ViewDrawingClippingTests () : FakeDriverBase
  6. {
  7. #region GetClip / SetClip Tests
  8. [Fact]
  9. public void GetClip_ReturnsDriverClip ()
  10. {
  11. IDriver driver = CreateFakeDriver (80, 25);
  12. var region = new Region (new Rectangle (10, 10, 20, 20));
  13. driver.Clip = region;
  14. View view = new () { Driver = driver };
  15. Region? result = view.GetClip ();
  16. Assert.NotNull (result);
  17. Assert.Equal (region, result);
  18. }
  19. [Fact]
  20. public void SetClip_NullRegion_DoesNothing ()
  21. {
  22. IDriver driver = CreateFakeDriver (80, 25);
  23. var original = new Region (new Rectangle (5, 5, 10, 10));
  24. driver.Clip = original;
  25. View view = new () { Driver = driver };
  26. view.SetClip (null);
  27. Assert.Equal (original, driver.Clip);
  28. }
  29. [Fact]
  30. public void SetClip_ValidRegion_SetsDriverClip ()
  31. {
  32. IDriver driver = CreateFakeDriver (80, 25);
  33. var region = new Region (new Rectangle (10, 10, 30, 30));
  34. View view = new () { Driver = driver };
  35. view.SetClip (region);
  36. Assert.Equal (region, driver.Clip);
  37. }
  38. #endregion
  39. #region SetClipToScreen Tests
  40. [Fact]
  41. public void SetClipToScreen_ReturnsPreviousClip ()
  42. {
  43. IDriver driver = CreateFakeDriver (80, 25);
  44. var original = new Region (new Rectangle (5, 5, 10, 10));
  45. driver.Clip = original;
  46. View view = new () { Driver = driver };
  47. Region? previous = view.SetClipToScreen ();
  48. Assert.Equal (original, previous);
  49. Assert.NotEqual (original, driver.Clip);
  50. Application.ResetState (true);
  51. }
  52. [Fact]
  53. public void SetClipToScreen_SetsClipToScreen ()
  54. {
  55. IDriver driver = CreateFakeDriver (80, 25);
  56. Application.Driver = driver;
  57. View view = new () { Driver = driver };
  58. view.SetClipToScreen ();
  59. Assert.NotNull (driver.Clip);
  60. Assert.Equal (driver.Screen, driver.Clip.GetBounds ());
  61. Application.ResetState (true);
  62. }
  63. #endregion
  64. #region ExcludeFromClip Tests
  65. [Fact]
  66. public void ExcludeFromClip_Rectangle_NullDriver_DoesNotThrow ()
  67. {
  68. View view = new () { Driver = null };
  69. var exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (5, 5, 10, 10)));
  70. Assert.Null (exception);
  71. Application.ResetState (true);
  72. }
  73. [Fact]
  74. public void ExcludeFromClip_Rectangle_ExcludesArea ()
  75. {
  76. IDriver driver = CreateFakeDriver (80, 25);
  77. driver.Clip = new Region (new Rectangle (0, 0, 80, 25));
  78. Application.Driver = driver;
  79. View view = new () { Driver = driver };
  80. var toExclude = new Rectangle (10, 10, 20, 20);
  81. view.ExcludeFromClip (toExclude);
  82. // Verify the region was excluded
  83. Assert.NotNull (driver.Clip);
  84. Assert.False (driver.Clip.Contains (15, 15));
  85. Application.ResetState (true);
  86. }
  87. [Fact]
  88. public void ExcludeFromClip_Region_NullDriver_DoesNotThrow ()
  89. {
  90. View view = new () { Driver = null };
  91. var exception = Record.Exception (() => view.ExcludeFromClip (new Region (new Rectangle (5, 5, 10, 10))));
  92. Assert.Null (exception);
  93. Application.ResetState (true);
  94. }
  95. [Fact]
  96. public void ExcludeFromClip_Region_ExcludesArea ()
  97. {
  98. IDriver driver = CreateFakeDriver (80, 25);
  99. driver.Clip = new Region (new Rectangle (0, 0, 80, 25));
  100. View view = new () { Driver = driver };
  101. var toExclude = new Region (new Rectangle (10, 10, 20, 20));
  102. view.ExcludeFromClip (toExclude);
  103. // Verify the region was excluded
  104. Assert.NotNull (driver.Clip);
  105. Assert.False (driver.Clip.Contains (15, 15));
  106. Application.ResetState (true);
  107. }
  108. #endregion
  109. #region AddFrameToClip Tests
  110. [Fact]
  111. public void AddFrameToClip_NullDriver_ReturnsNull ()
  112. {
  113. var view = new View { X = 0, Y = 0, Width = 10, Height = 10 };
  114. view.BeginInit ();
  115. view.EndInit ();
  116. Region? result = view.AddFrameToClip ();
  117. Assert.Null (result);
  118. }
  119. [Fact]
  120. public void AddFrameToClip_IntersectsWithFrame ()
  121. {
  122. IDriver driver = CreateFakeDriver (80, 25);
  123. driver.Clip = new Region (driver.Screen);
  124. var view = new View
  125. {
  126. X = 1,
  127. Y = 1,
  128. Width = 20,
  129. Height = 20,
  130. Driver = driver
  131. };
  132. view.BeginInit ();
  133. view.EndInit ();
  134. view.LayoutSubViews ();
  135. Region? previous = view.AddFrameToClip ();
  136. Assert.NotNull (previous);
  137. Assert.NotNull (driver.Clip);
  138. // The clip should now be the intersection of the screen and the view's frame
  139. Rectangle expectedBounds = new Rectangle (1, 1, 20, 20);
  140. Assert.Equal (expectedBounds, driver.Clip.GetBounds ());
  141. }
  142. #endregion
  143. #region AddViewportToClip Tests
  144. [Fact]
  145. public void AddViewportToClip_NullDriver_ReturnsNull ()
  146. {
  147. var view = new View { X = 0, Y = 0, Width = 10, Height = 10 };
  148. view.BeginInit ();
  149. view.EndInit ();
  150. Region? result = view.AddViewportToClip ();
  151. Assert.Null (result);
  152. }
  153. [Fact]
  154. public void AddViewportToClip_IntersectsWithViewport ()
  155. {
  156. IDriver driver = CreateFakeDriver (80, 25);
  157. driver.Clip = new Region (driver.Screen);
  158. var view = new View
  159. {
  160. X = 1,
  161. Y = 1,
  162. Width = 20,
  163. Height = 20,
  164. Driver = driver
  165. };
  166. view.BeginInit ();
  167. view.EndInit ();
  168. view.LayoutSubViews ();
  169. Region? previous = view.AddViewportToClip ();
  170. Assert.NotNull (previous);
  171. Assert.NotNull (driver.Clip);
  172. // The clip should be the viewport area
  173. Rectangle viewportScreen = view.ViewportToScreen (new Rectangle (Point.Empty, view.Viewport.Size));
  174. Assert.Equal (viewportScreen, driver.Clip.GetBounds ());
  175. }
  176. [Fact]
  177. public void AddViewportToClip_WithClipContentOnly_LimitsToVisibleContent ()
  178. {
  179. IDriver driver = CreateFakeDriver (80, 25);
  180. driver.Clip = new Region (driver.Screen);
  181. var view = new View
  182. {
  183. X = 1,
  184. Y = 1,
  185. Width = 20,
  186. Height = 20,
  187. Driver = driver
  188. };
  189. view.SetContentSize (new Size (100, 100));
  190. view.ViewportSettings = ViewportSettingsFlags.ClipContentOnly;
  191. view.BeginInit ();
  192. view.EndInit ();
  193. view.LayoutSubViews ();
  194. Region? previous = view.AddViewportToClip ();
  195. Assert.NotNull (previous);
  196. Assert.NotNull (driver.Clip);
  197. // The clip should be limited to visible content
  198. Rectangle visibleContent = view.ViewportToScreen (new Rectangle (new (-view.Viewport.X, -view.Viewport.Y), view.GetContentSize ()));
  199. Rectangle viewport = view.ViewportToScreen (new Rectangle (Point.Empty, view.Viewport.Size));
  200. Rectangle expected = Rectangle.Intersect (viewport, visibleContent);
  201. Assert.Equal (expected, driver.Clip.GetBounds ());
  202. }
  203. #endregion
  204. #region Clip Interaction Tests
  205. [Fact]
  206. public void ClipRegions_StackCorrectly_WithNestedViews ()
  207. {
  208. IDriver driver = CreateFakeDriver (100, 100);
  209. driver.Clip = new Region (driver.Screen);
  210. var superView = new View
  211. {
  212. X = 1,
  213. Y = 1,
  214. Width = 50,
  215. Height = 50,
  216. Driver = driver
  217. };
  218. superView.BeginInit ();
  219. superView.EndInit ();
  220. var view = new View
  221. {
  222. X = 5,
  223. Y = 5,
  224. Width = 30,
  225. Height = 30,
  226. };
  227. superView.Add (view);
  228. superView.LayoutSubViews ();
  229. // Set clip to superView's frame
  230. Region? superViewClip = superView.AddFrameToClip ();
  231. Rectangle superViewBounds = driver.Clip.GetBounds ();
  232. // Now set clip to view's frame
  233. Region? viewClip = view.AddFrameToClip ();
  234. Rectangle viewBounds = driver.Clip.GetBounds ();
  235. // Child clip should be within superView clip
  236. Assert.True (superViewBounds.Contains (viewBounds.Location));
  237. // Restore superView clip
  238. view.SetClip (superViewClip);
  239. // Assert.Equal (superViewBounds, driver.Clip.GetBounds ());
  240. }
  241. [Fact]
  242. public void ClipRegions_RespectPreviousClip ()
  243. {
  244. IDriver driver = CreateFakeDriver (80, 25);
  245. var initialClip = new Region (new Rectangle (20, 20, 40, 40));
  246. driver.Clip = initialClip;
  247. var view = new View
  248. {
  249. X = 1,
  250. Y = 1,
  251. Width = 60,
  252. Height = 60,
  253. Driver = driver
  254. };
  255. view.BeginInit ();
  256. view.EndInit ();
  257. view.LayoutSubViews ();
  258. Region? previous = view.AddFrameToClip ();
  259. // The new clip should be the intersection of the initial clip and the view's frame
  260. Rectangle expected = Rectangle.Intersect (
  261. initialClip.GetBounds (),
  262. view.FrameToScreen ()
  263. );
  264. Assert.Equal (expected, driver.Clip.GetBounds ());
  265. // Restore should give us back the original
  266. view.SetClip (previous);
  267. Assert.Equal (initialClip.GetBounds (), driver.Clip.GetBounds ());
  268. }
  269. #endregion
  270. #region Edge Cases
  271. [Fact]
  272. public void AddFrameToClip_EmptyFrame_WorksCorrectly ()
  273. {
  274. IDriver driver = CreateFakeDriver (80, 25);
  275. driver.Clip = new Region (driver.Screen);
  276. var view = new View
  277. {
  278. X = 1,
  279. Y = 1,
  280. Width = 0,
  281. Height = 0,
  282. Driver = driver
  283. };
  284. view.BeginInit ();
  285. view.EndInit ();
  286. view.LayoutSubViews ();
  287. Region? previous = view.AddFrameToClip ();
  288. Assert.NotNull (previous);
  289. Assert.NotNull (driver.Clip);
  290. }
  291. [Fact]
  292. public void AddViewportToClip_EmptyViewport_WorksCorrectly ()
  293. {
  294. IDriver driver = CreateFakeDriver (80, 25);
  295. driver.Clip = new Region (driver.Screen);
  296. var view = new View
  297. {
  298. X = 1,
  299. Y = 1,
  300. Width = 1, // Minimal size to have adornments
  301. Height = 1,
  302. Driver = driver
  303. };
  304. view.Border!.Thickness = new Thickness (1);
  305. view.BeginInit ();
  306. view.EndInit ();
  307. view.LayoutSubViews ();
  308. // With border thickness of 1, the viewport should be empty
  309. Assert.True (view.Viewport.Size.Width == 0 || view.Viewport.Size.Height == 0);
  310. Region? previous = view.AddViewportToClip ();
  311. Assert.NotNull (previous);
  312. }
  313. [Fact]
  314. public void ClipRegions_OutOfBounds_HandledCorrectly ()
  315. {
  316. IDriver driver = CreateFakeDriver (80, 25);
  317. driver.Clip = new Region (driver.Screen);
  318. var view = new View
  319. {
  320. X = 100, // Outside screen bounds
  321. Y = 100,
  322. Width = 20,
  323. Height = 20,
  324. Driver = driver
  325. };
  326. view.BeginInit ();
  327. view.EndInit ();
  328. view.LayoutSubViews ();
  329. Region? previous = view.AddFrameToClip ();
  330. Assert.NotNull (previous);
  331. // The clip should be empty since the view is outside the screen
  332. Assert.True (driver.Clip.IsEmpty () || !driver.Clip.Contains (100, 100));
  333. }
  334. #endregion
  335. #region Drawing Tests
  336. [Fact]
  337. public void Clip_Set_BeforeDraw_ClipsDrawing ()
  338. {
  339. IDriver driver = CreateFakeDriver (80, 25);
  340. var clip = new Region (new Rectangle (10, 10, 10, 10));
  341. driver.Clip = clip;
  342. var view = new View
  343. {
  344. X = 0,
  345. Y = 0,
  346. Width = 50,
  347. Height = 50,
  348. Driver = driver
  349. };
  350. view.BeginInit ();
  351. view.EndInit ();
  352. view.LayoutSubViews ();
  353. view.Draw ();
  354. // Verify clip was used
  355. Assert.NotNull (driver.Clip);
  356. }
  357. [Fact]
  358. public void Draw_UpdatesDriverClip ()
  359. {
  360. IDriver driver = CreateFakeDriver (80, 25);
  361. driver.Clip = new Region (driver.Screen);
  362. var view = new View
  363. {
  364. X = 1,
  365. Y = 1,
  366. Width = 20,
  367. Height = 20,
  368. Driver = driver
  369. };
  370. view.BeginInit ();
  371. view.EndInit ();
  372. view.LayoutSubViews ();
  373. view.Draw ();
  374. // Clip should be updated to exclude the drawn view
  375. Assert.NotNull (driver.Clip);
  376. // Assert.False (driver.Clip.Contains (15, 15)); // Point inside the view should be excluded
  377. }
  378. [Fact]
  379. public void Draw_WithSubViews_ClipsCorrectly ()
  380. {
  381. IDriver driver = CreateFakeDriver (80, 25);
  382. driver.Clip = new Region (driver.Screen);
  383. var superView = new View
  384. {
  385. X = 1,
  386. Y = 1,
  387. Width = 50,
  388. Height = 50,
  389. Driver = driver
  390. };
  391. var view = new View { X = 5, Y = 5, Width = 20, Height = 20 };
  392. superView.Add (view);
  393. superView.BeginInit ();
  394. superView.EndInit ();
  395. superView.LayoutSubViews ();
  396. superView.Draw ();
  397. // Both superView and view should be excluded from clip
  398. Assert.NotNull (driver.Clip);
  399. // Assert.False (driver.Clip.Contains (15, 15)); // Point in superView should be excluded
  400. }
  401. [Fact]
  402. public void Draw_NonVisibleView_DoesNotUpdateClip ()
  403. {
  404. IDriver driver = CreateFakeDriver (80, 25);
  405. var originalClip = new Region (driver.Screen);
  406. driver.Clip = originalClip.Clone ();
  407. var view = new View
  408. {
  409. X = 10,
  410. Y = 10,
  411. Width = 20,
  412. Height = 20,
  413. Visible = false,
  414. Driver = driver
  415. };
  416. view.BeginInit ();
  417. view.EndInit ();
  418. view.Draw ();
  419. // Clip should not be modified for invisible views
  420. Assert.True (driver.Clip.Equals (originalClip));
  421. }
  422. [Fact]
  423. public void ExcludeFromClip_ExcludesRegion ()
  424. {
  425. IDriver driver = CreateFakeDriver (80, 25);
  426. driver.Clip = new Region (driver.Screen);
  427. Application.Driver = driver;
  428. var view = new View
  429. {
  430. X = 10,
  431. Y = 10,
  432. Width = 20,
  433. Height = 20,
  434. Driver = driver
  435. };
  436. view.BeginInit ();
  437. view.EndInit ();
  438. view.LayoutSubViews ();
  439. var excludeRect = new Rectangle (15, 15, 10, 10);
  440. view.ExcludeFromClip (excludeRect);
  441. Assert.NotNull (driver.Clip);
  442. Assert.False (driver.Clip.Contains (20, 20)); // Point inside excluded rect should not be in clip
  443. Application.ResetState (true);
  444. }
  445. [Fact]
  446. public void ExcludeFromClip_WithNullClip_DoesNotThrow ()
  447. {
  448. IDriver driver = CreateFakeDriver (80, 25);
  449. driver.Clip = null!;
  450. Application.Driver = driver;
  451. var view = new View
  452. {
  453. X = 10,
  454. Y = 10,
  455. Width = 20,
  456. Height = 20,
  457. Driver = driver
  458. };
  459. var exception = Record.Exception (() => view.ExcludeFromClip (new Rectangle (15, 15, 10, 10)));
  460. Assert.Null (exception);
  461. Application.ResetState (true);
  462. }
  463. #endregion
  464. #region Misc Tests
  465. [Fact]
  466. public void SetClip_SetsDriverClip ()
  467. {
  468. IDriver driver = CreateFakeDriver (80, 25);
  469. var view = new View
  470. {
  471. X = 10,
  472. Y = 10,
  473. Width = 20,
  474. Height = 20,
  475. Driver = driver
  476. };
  477. var newClip = new Region (new Rectangle (5, 5, 30, 30));
  478. view.SetClip (newClip);
  479. Assert.Equal (newClip, driver.Clip);
  480. }
  481. [Fact (Skip = "See BUGBUG in SetClip")]
  482. public void SetClip_WithNullClip_ClearsClip ()
  483. {
  484. IDriver driver = CreateFakeDriver (80, 25);
  485. driver.Clip = new Region (new Rectangle (10, 10, 20, 20));
  486. var view = new View
  487. {
  488. X = 10,
  489. Y = 10,
  490. Width = 20,
  491. Height = 20,
  492. Driver = driver
  493. };
  494. view.SetClip (null);
  495. Assert.Null (driver.Clip);
  496. }
  497. [Fact]
  498. public void Draw_Excludes_View_From_Clip ()
  499. {
  500. IDriver driver = CreateFakeDriver (80, 25);
  501. var originalClip = new Region (driver.Screen);
  502. driver.Clip = originalClip.Clone ();
  503. var view = new View
  504. {
  505. X = 10,
  506. Y = 10,
  507. Width = 20,
  508. Height = 20,
  509. Driver = driver
  510. };
  511. view.BeginInit ();
  512. view.EndInit ();
  513. view.LayoutSubViews ();
  514. Region clipWithViewExcluded = originalClip.Clone ();
  515. clipWithViewExcluded.Exclude (view.Frame);
  516. view.Draw ();
  517. Assert.Equal (clipWithViewExcluded, driver.Clip);
  518. Assert.NotNull (driver.Clip);
  519. }
  520. [Fact]
  521. public void Draw_EmptyViewport_DoesNotCrash ()
  522. {
  523. IDriver driver = CreateFakeDriver (80, 25);
  524. driver.Clip = new Region (driver.Screen);
  525. var view = new View
  526. {
  527. X = 10,
  528. Y = 10,
  529. Width = 1,
  530. Height = 1,
  531. Driver = driver
  532. };
  533. view.Border!.Thickness = new Thickness (1);
  534. view.BeginInit ();
  535. view.EndInit ();
  536. view.LayoutSubViews ();
  537. // With border of 1, viewport should be empty (0x0 or negative)
  538. var exception = Record.Exception (() => view.Draw ());
  539. Assert.Null (exception);
  540. }
  541. [Fact]
  542. public void Draw_VeryLargeView_HandlesClippingCorrectly ()
  543. {
  544. IDriver driver = CreateFakeDriver (80, 25);
  545. driver.Clip = new Region (driver.Screen);
  546. var view = new View
  547. {
  548. X = 0,
  549. Y = 0,
  550. Width = 1000,
  551. Height = 1000,
  552. Driver = driver
  553. };
  554. view.BeginInit ();
  555. view.EndInit ();
  556. view.LayoutSubViews ();
  557. var exception = Record.Exception (() => view.Draw ());
  558. Assert.Null (exception);
  559. }
  560. [Fact]
  561. public void Draw_NegativeCoordinates_HandlesClippingCorrectly ()
  562. {
  563. IDriver driver = CreateFakeDriver (80, 25);
  564. driver.Clip = new Region (driver.Screen);
  565. var view = new View
  566. {
  567. X = -10,
  568. Y = -10,
  569. Width = 50,
  570. Height = 50,
  571. Driver = driver
  572. };
  573. view.BeginInit ();
  574. view.EndInit ();
  575. view.LayoutSubViews ();
  576. var exception = Record.Exception (() => view.Draw ());
  577. Assert.Null (exception);
  578. }
  579. [Fact]
  580. public void Draw_OutOfScreenBounds_HandlesClippingCorrectly ()
  581. {
  582. IDriver driver = CreateFakeDriver (80, 25);
  583. driver.Clip = new Region (driver.Screen);
  584. var view = new View
  585. {
  586. X = 100,
  587. Y = 100,
  588. Width = 50,
  589. Height = 50,
  590. Driver = driver
  591. };
  592. view.BeginInit ();
  593. view.EndInit ();
  594. view.LayoutSubViews ();
  595. var exception = Record.Exception (() => view.Draw ());
  596. Assert.Null (exception);
  597. }
  598. #endregion
  599. }