ViewDrawingClippingTests.cs 19 KB

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