RegionTests.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  1. namespace Terminal.Gui.DrawingTests;
  2. public class RegionTests
  3. {
  4. [Fact]
  5. public void Clone_CreatesExactCopy ()
  6. {
  7. var region = new Region (new (10, 10, 50, 50));
  8. Region clone = region.Clone ();
  9. Assert.True (clone.Contains (20, 20));
  10. Assert.Equal (region.GetRectangles (), clone.GetRectangles ());
  11. }
  12. [Fact]
  13. public void Combine_EmptyRectangles_ProducesEmptyRegion ()
  14. {
  15. // Arrange: Create region and combine with empty rectangles
  16. var region = new Region ();
  17. region.Combine (new Rectangle (0, 0, 0, 0), RegionOp.Union); // Empty rectangle
  18. region.Combine (new Region (), RegionOp.Union); // Empty region
  19. // Assert: Region is empty
  20. Assert.Empty (region.GetRectangles ());
  21. }
  22. [Fact]
  23. public void Complement_Rectangle_ComplementsRegion ()
  24. {
  25. var region = new Region (new (10, 10, 50, 50));
  26. region.Complement (new (0, 0, 100, 100));
  27. Assert.True (region.Contains (5, 5));
  28. Assert.False (region.Contains (20, 20));
  29. }
  30. [Theory]
  31. [MemberData (nameof (Complement_TestData))]
  32. public void Complement_Region_Success (Region region, Rectangle [] rectangles, Rectangle [] expectedScans)
  33. {
  34. foreach (Rectangle rect in rectangles)
  35. {
  36. region.Complement (rect);
  37. }
  38. Rectangle [] actualScans = region.GetRectangles ();
  39. Assert.Equal (expectedScans, actualScans);
  40. }
  41. public static IEnumerable<object []> Complement_TestData ()
  42. {
  43. yield return new object []
  44. {
  45. new Region (new (10, 10, 100, 100)),
  46. new Rectangle [] { new (40, 60, 100, 20) },
  47. new Rectangle [] { new (110, 60, 30, 20) }
  48. };
  49. yield return new object []
  50. {
  51. new Region (new (70, 10, 100, 100)),
  52. new Rectangle [] { new (40, 60, 100, 20) },
  53. new Rectangle [] { new (40, 60, 30, 20) }
  54. };
  55. yield return new object []
  56. {
  57. new Region (new (40, 100, 100, 100)),
  58. new Rectangle [] { new (70, 80, 50, 40) },
  59. new Rectangle [] { new (70, 80, 50, 20) }
  60. };
  61. yield return new object []
  62. {
  63. new Region (new (40, 10, 100, 100)),
  64. new Rectangle [] { new (70, 80, 50, 40) },
  65. new Rectangle [] { new (70, 110, 50, 10) }
  66. };
  67. yield return new object []
  68. {
  69. new Region (new (30, 30, 80, 80)),
  70. new Rectangle []
  71. {
  72. new (45, 45, 200, 200),
  73. new (160, 260, 10, 10),
  74. new (170, 260, 10, 10)
  75. },
  76. new Rectangle [] { new (170, 260, 10, 10) }
  77. };
  78. yield return new object []
  79. {
  80. new Region (),
  81. new [] { Rectangle.Empty },
  82. new Rectangle[0]
  83. };
  84. yield return new object []
  85. {
  86. new Region (),
  87. new Rectangle [] { new (1, 2, 3, 4) },
  88. new Rectangle[0]
  89. };
  90. }
  91. [Fact]
  92. public void Complement_WithRectangle_ComplementsRegion ()
  93. {
  94. var region = new Region (new (10, 10, 50, 50));
  95. var rect = new Rectangle (0, 0, 100, 100);
  96. region.Complement (rect);
  97. // Points that were inside the original region should now be outside
  98. Assert.False (region.Contains (35, 35));
  99. // Points that were outside the original region but inside bounds should now be inside
  100. Assert.True (region.Contains (5, 5));
  101. Assert.True (region.Contains (95, 95));
  102. }
  103. [Fact]
  104. public void Complement_WithRegion_ComplementsRegion ()
  105. {
  106. var region = new Region (new (10, 10, 50, 50));
  107. var bounds = new Rectangle (0, 0, 100, 100);
  108. region.Complement (bounds);
  109. // Points that were inside the original region should now be outside
  110. Assert.False (region.Contains (35, 35));
  111. // Points that were outside the original region but inside bounds should now be inside
  112. Assert.True (region.Contains (5, 5));
  113. Assert.True (region.Contains (95, 95));
  114. }
  115. [Fact]
  116. public void Constructor_EmptyRegion_IsEmpty ()
  117. {
  118. var region = new Region ();
  119. Assert.True (region.IsEmpty ());
  120. }
  121. [Fact]
  122. public void Constructor_WithRectangle_IsNotEmpty ()
  123. {
  124. var region = new Region (new (10, 10, 50, 50));
  125. Assert.False (region.IsEmpty ());
  126. }
  127. [Fact]
  128. public void Contains_Point_ReturnsCorrectResult ()
  129. {
  130. var region = new Region (new (10, 10, 50, 50));
  131. Assert.True (region.Contains (20, 20));
  132. Assert.False (region.Contains (100, 100));
  133. }
  134. [Fact]
  135. public void Contains_PointInsideRegion_ReturnsTrue ()
  136. {
  137. var region = new Region (new (10, 10, 50, 50));
  138. Assert.True (region.Contains (20, 20));
  139. }
  140. [Fact]
  141. public void Contains_RectangleInsideRegion_ReturnsTrue ()
  142. {
  143. var region = new Region (new (10, 10, 50, 50));
  144. Assert.True (region.Contains (new (20, 20, 10, 10)));
  145. }
  146. [Fact]
  147. public void Equals_NullRegion_ReturnsFalse ()
  148. {
  149. var region = new Region ();
  150. Assert.False (region.Equals (null));
  151. }
  152. [Fact]
  153. public void Equals_SameRegion_ReturnsTrue ()
  154. {
  155. var region = new Region (new (1, 2, 3, 4));
  156. Assert.True (region.Equals (region));
  157. }
  158. public static IEnumerable<object []> Equals_TestData ()
  159. {
  160. static Region Empty ()
  161. {
  162. Region emptyRegion = new ();
  163. emptyRegion.Intersect (Rectangle.Empty);
  164. return emptyRegion;
  165. }
  166. yield return new object [] { new Region (), new Region (), true };
  167. yield return new object [] { new Region (), Empty (), true };
  168. yield return new object [] { new Region (), new Region (new (1, 2, 3, 4)), false };
  169. yield return new object [] { Empty (), Empty (), true };
  170. yield return new object [] { Empty (), new Region (new (0, 0, 0, 0)), true };
  171. yield return new object [] { Empty (), new Region (new (1, 2, 3, 3)), false };
  172. yield return new object [] { new Region (new (1, 2, 3, 4)), new Region (new (1, 2, 3, 4)), true };
  173. yield return new object [] { new Region (new (1, 2, 3, 4)), new Region (new (2, 2, 3, 4)), false };
  174. yield return new object [] { new Region (new (1, 2, 3, 4)), new Region (new (1, 3, 3, 4)), false };
  175. yield return new object [] { new Region (new (1, 2, 3, 4)), new Region (new (1, 2, 4, 4)), false };
  176. yield return new object [] { new Region (new (1, 2, 3, 4)), new Region (new (1, 2, 3, 5)), false };
  177. }
  178. [Theory]
  179. [MemberData (nameof (Equals_TestData))]
  180. public void Equals_Valid_ReturnsExpected (Region region1, Region region2, bool expected) { Assert.Equal (expected, region1.Equals (region2)); }
  181. [Fact]
  182. public void GetBounds_ReturnsBoundingRectangle ()
  183. {
  184. var region = new Region (new (10, 10, 50, 50));
  185. region.Union (new Rectangle (100, 100, 20, 20));
  186. Rectangle bounds = region.GetBounds ();
  187. Assert.Equal (new (10, 10, 110, 110), bounds);
  188. }
  189. [Fact]
  190. public void GetBounds_ReturnsCorrectBounds ()
  191. {
  192. var region = new Region ();
  193. region.Union (new Rectangle (10, 10, 50, 50));
  194. region.Union (new Rectangle (30, 30, 50, 50));
  195. Rectangle bounds = region.GetBounds ();
  196. Assert.Equal (new (10, 10, 70, 70), bounds);
  197. }
  198. [Fact]
  199. public void GetRegionScans_ReturnsAllRectangles ()
  200. {
  201. var region = new Region (new (10, 10, 50, 50));
  202. region.Union (new Rectangle (100, 100, 20, 20));
  203. Rectangle [] scans = region.GetRectangles ();
  204. Assert.Equal (2, scans.Length);
  205. Assert.Contains (new (10, 10, 50, 50), scans);
  206. Assert.Contains (new (100, 100, 20, 20), scans);
  207. }
  208. [Fact]
  209. public void Intersect_Rectangle_IntersectsRegion ()
  210. {
  211. var region = new Region (new (10, 10, 50, 50));
  212. region.Intersect (new Rectangle (30, 30, 50, 50));
  213. Assert.False (region.Contains (20, 20));
  214. Assert.True (region.Contains (40, 40));
  215. }
  216. [Fact]
  217. public void Intersect_Region_IntersectsRegions ()
  218. {
  219. var region1 = new Region (new (10, 10, 50, 50));
  220. var region2 = new Region (new (30, 30, 50, 50));
  221. region1.Intersect (region2);
  222. Assert.False (region1.Contains (20, 20));
  223. Assert.True (region1.Contains (40, 40));
  224. }
  225. [Fact]
  226. public void Intersect_WithEmptyRectangle_ResultsInEmptyRegion ()
  227. {
  228. // Arrange
  229. var region = new Region (new (0, 0, 10, 10));
  230. var rectangle = Rectangle.Empty; // Use Empty instead of 0-size
  231. // Act
  232. region.Intersect (rectangle);
  233. // Assert
  234. Assert.True (region.IsEmpty ());
  235. }
  236. [Theory]
  237. [InlineData (0, 0, 0, 0)] // Empty by zero size
  238. [InlineData (0, 0, 0, 10)] // Empty by zero width
  239. [InlineData (0, 0, 10, 0)] // Empty by zero height
  240. [InlineData (-5, -5, 0, 0)] // Empty by zero size at negative coords
  241. [InlineData (10, 10, -5, -5)] // Empty by negative size
  242. public void Intersect_WithEmptyRegion_ResultsInEmptyRegion (int x, int y, int width, int height)
  243. {
  244. // Arrange
  245. var region = new Region ();
  246. region.Union (new Rectangle (0, 0, 10, 10));
  247. region.Union (new Rectangle (20, 0, 10, 10));
  248. // Create a region that should be considered empty
  249. var emptyRegion = new Region ();
  250. if (width <= 0 || height <= 0)
  251. {
  252. // For negative or zero dimensions, use an empty region
  253. emptyRegion = new ();
  254. }
  255. else
  256. {
  257. emptyRegion = new (new (x, y, width, height));
  258. }
  259. // Verify initial states
  260. Assert.Equal (2, region.GetRectangles ().Length);
  261. Assert.True (emptyRegion.IsEmpty ());
  262. // Act
  263. region.Intersect (emptyRegion);
  264. // Assert
  265. Assert.True (region.IsEmpty ());
  266. Assert.Empty (region.GetRectangles ());
  267. }
  268. [Fact]
  269. public void Intersect_WithFullyContainedRectangle_ResultsInSmallerRegion ()
  270. {
  271. // Arrange
  272. var region = new Region (new (0, 0, 10, 10));
  273. var rectangle = new Rectangle (2, 2, 4, 4);
  274. // Act
  275. region.Intersect (rectangle);
  276. // Assert
  277. Assert.Single (region.GetRectangles ());
  278. Assert.Equal (new (2, 2, 4, 4), region.GetRectangles () [0]);
  279. }
  280. [Fact]
  281. public void Intersect_WithMultipleRectanglesInRegion_IntersectsAll ()
  282. {
  283. // Arrange
  284. var region = new Region ();
  285. region.Union (new Rectangle (0, 0, 5, 5));
  286. region.Union (new Rectangle (10, 0, 5, 5));
  287. var rectangle = new Rectangle (2, 2, 10, 2);
  288. // Act
  289. region.Intersect (rectangle);
  290. // Assert
  291. Assert.Equal (2, region.GetRectangles ().Length);
  292. Assert.Contains (new (2, 2, 3, 2), region.GetRectangles ());
  293. Assert.Contains (new (10, 2, 2, 2), region.GetRectangles ());
  294. }
  295. //[Fact]
  296. //public void Intersect_WithEmptyRegion_ResultsInEmptyRegion ()
  297. //{
  298. // // Arrange
  299. // var region = new Region ();
  300. // var rectangle = new Rectangle (0, 0, 10, 10);
  301. // // Act
  302. // region.Intersect (rectangle);
  303. // // Assert
  304. // Assert.True (region.IsEmpty ());
  305. //}
  306. [Fact]
  307. public void Intersect_WithNonOverlappingRectangle_ResultsInEmptyRegion ()
  308. {
  309. // Arrange
  310. var region = new Region (new (0, 0, 5, 5));
  311. var rectangle = new Rectangle (10, 10, 5, 5);
  312. // Act
  313. region.Intersect (rectangle);
  314. // Assert
  315. Assert.True (region.IsEmpty ());
  316. }
  317. [Fact]
  318. public void Intersect_WithNullRegion_ResultsInEmptyRegion ()
  319. {
  320. // Arrange
  321. var region = new Region ();
  322. region.Union (new Rectangle (0, 0, 10, 10));
  323. region.Union (new Rectangle (20, 0, 10, 10));
  324. // Verify initial state
  325. Assert.Equal (2, region.GetRectangles ().Length);
  326. // Act
  327. region.Intersect (null);
  328. // Assert
  329. Assert.True (region.IsEmpty ());
  330. Assert.Empty (region.GetRectangles ());
  331. }
  332. [Fact]
  333. public void Intersect_WithPartiallyOverlappingRectangle_ResultsInIntersectedRegion ()
  334. {
  335. // Arrange
  336. var region = new Region (new (0, 0, 5, 5));
  337. var rectangle = new Rectangle (2, 2, 5, 5);
  338. // Act
  339. region.Intersect (rectangle);
  340. // Assert
  341. Assert.Single (region.GetRectangles ());
  342. Assert.Equal (new (2, 2, 3, 3), region.GetRectangles () [0]);
  343. }
  344. [Fact]
  345. public void Intersect_WithRectangle_IntersectsRectangles ()
  346. {
  347. var region = new Region (new (10, 10, 50, 50));
  348. var rect = new Rectangle (30, 30, 50, 50);
  349. region.Intersect (rect);
  350. Assert.True (region.Contains (35, 35));
  351. Assert.False (region.Contains (20, 20));
  352. }
  353. [Fact]
  354. public void Intersect_WithRegion_IntersectsRegions ()
  355. {
  356. var region1 = new Region (new (10, 10, 50, 50));
  357. var region2 = new Region (new (30, 30, 50, 50));
  358. region1.Intersect (region2.GetBounds ());
  359. Assert.True (region1.Contains (35, 35));
  360. Assert.False (region1.Contains (20, 20));
  361. }
  362. [Fact]
  363. public void Intersect_ImmediateNormalization_AffectsRectangleOrder ()
  364. {
  365. // Create a region with two overlapping rectangles
  366. var region1 = new Region (new (0, 0, 4, 4)); // 0,0 to 4,4
  367. // Intersect with a region that partially overlaps
  368. var region2 = new Region (new (2, 2, 4, 4)); // 2,2 to 6,6
  369. region1.Intersect (region2);
  370. // Get the resulting rectangles
  371. Rectangle [] result = region1.GetRectangles ();
  372. // Expected behavior from original Region:
  373. // Intersect immediately produces a single rectangle (2,2,2,2)
  374. Assert.Single (result); // Original has 1 rectangle due to immediate processing
  375. Assert.Equal (new (2, 2, 2, 2), result [0]);
  376. // My updated Region defers normalization after Intersect,
  377. // so GetRectangles() might merge differently or preserve order differently,
  378. // potentially failing the exact match or count due to _isDirty
  379. }
  380. [Fact]
  381. public void IsEmpty_AfterClear_ReturnsTrue ()
  382. {
  383. // Arrange
  384. var region = new Region (new (0, 0, 10, 10));
  385. // Act
  386. region.Intersect (Rectangle.Empty);
  387. // Assert
  388. Assert.True (region.IsEmpty ());
  389. Assert.Empty (region.GetRectangles ());
  390. }
  391. [Fact]
  392. public void IsEmpty_AfterComplement_ReturnsCorrectState ()
  393. {
  394. // Test 1: Complement a region with bounds that fully contain it
  395. var region = new Region (new (2, 2, 5, 5)); // Small inner rectangle
  396. region.Complement (new (0, 0, 10, 10)); // Larger outer bounds
  397. Assert.False (region.IsEmpty ()); // Should have area around the original rectangle
  398. // Test 2: Complement with bounds equal to the region
  399. region = new (new (0, 0, 10, 10));
  400. region.Complement (new (0, 0, 10, 10));
  401. Assert.True (region.IsEmpty ()); // Should be empty as there's no area left
  402. // Test 3: Complement with empty bounds
  403. region = new (new (0, 0, 10, 10));
  404. region.Complement (Rectangle.Empty);
  405. Assert.True (region.IsEmpty ()); // Should be empty as there's no bounds
  406. }
  407. [Fact]
  408. public void IsEmpty_AfterExclude_ReturnsTrue ()
  409. {
  410. // Arrange
  411. var region = new Region (new (0, 0, 10, 10));
  412. // Act
  413. region.Exclude (new Rectangle (0, 0, 10, 10));
  414. // Assert
  415. Assert.True (region.IsEmpty ());
  416. Assert.Empty (region.GetRectangles ());
  417. }
  418. [Fact]
  419. public void IsEmpty_AfterUnion_ReturnsFalse ()
  420. {
  421. // Arrange
  422. var region = new Region ();
  423. region.Union (new Rectangle (0, 0, 10, 10));
  424. // Assert
  425. Assert.False (region.IsEmpty ());
  426. Assert.Single (region.GetRectangles ());
  427. }
  428. [Fact]
  429. public void IsEmpty_EmptyRegion_ReturnsTrue ()
  430. {
  431. var region = new Region ();
  432. Assert.True (region.IsEmpty ());
  433. }
  434. [Fact]
  435. public void IsEmpty_MultipleOperations_ReturnsExpectedResult ()
  436. {
  437. // Arrange
  438. var region = new Region ();
  439. // Act & Assert - Should be empty initially
  440. Assert.True (region.IsEmpty ());
  441. // Add a rectangle - Should not be empty
  442. region.Union (new Rectangle (0, 0, 10, 10));
  443. Assert.False (region.IsEmpty ());
  444. // Exclude the same rectangle - Should be empty again
  445. region.Exclude (new Rectangle (0, 0, 10, 10));
  446. Assert.True (region.IsEmpty ());
  447. // Add two rectangles - Should not be empty
  448. region.Union (new Rectangle (0, 0, 5, 5));
  449. region.Union (new Rectangle (10, 10, 5, 5));
  450. Assert.False (region.IsEmpty ());
  451. }
  452. [Fact]
  453. public void IsEmpty_NewRegion_ReturnsTrue ()
  454. {
  455. // Arrange
  456. var region = new Region ();
  457. // Act & Assert
  458. Assert.True (region.IsEmpty ());
  459. Assert.Empty (region.GetRectangles ());
  460. }
  461. [Fact]
  462. public void IsEmpty_ReturnsCorrectResult ()
  463. {
  464. var region = new Region ();
  465. Assert.True (region.IsEmpty ());
  466. region.Union (new Rectangle (10, 10, 50, 50));
  467. Assert.False (region.IsEmpty ());
  468. }
  469. [Theory]
  470. [InlineData (0, 0, 1, 1)] // 1x1 at origin
  471. [InlineData (10, 10, 5, 5)] // 5x5 at (10,10)
  472. [InlineData (-5, -5, 10, 10)] // Negative coordinates
  473. public void IsEmpty_ValidRectangle_ReturnsFalse (int x, int y, int width, int height)
  474. {
  475. // Arrange
  476. var region = new Region (new (x, y, width, height));
  477. // Assert
  478. Assert.False (region.IsEmpty ());
  479. Assert.Single (region.GetRectangles ());
  480. }
  481. [Theory]
  482. [InlineData (0, 0, 0, 0)] // Zero size
  483. [InlineData (0, 0, 0, 10)] // Zero width
  484. [InlineData (0, 0, 10, 0)] // Zero height
  485. [InlineData (-5, -5, 0, 0)] // Zero size at negative coords
  486. public void IsEmpty_ZeroSizeRectangle_ReturnsCorrectState (int x, int y, int width, int height)
  487. {
  488. var region = new Region (new (x, y, width, height));
  489. // Only check IsEmpty() since Rectangle(0,0,0,0) is still stored
  490. Assert.True (region.IsEmpty ());
  491. }
  492. //[Fact]
  493. //public void MinimalUnion_SingleRectangle_DoesNotChange ()
  494. //{
  495. // var region = new Region (new Rectangle (0, 0, 10, 10));
  496. // region.MinimalUnion (new Rectangle (0, 0, 10, 10));
  497. // Assert.Single (region.GetRectangles ());
  498. // Assert.Equal (new Rectangle (0, 0, 10, 10), region.GetRectangles ().First ());
  499. //}
  500. //[Fact]
  501. //public void MinimalUnion_OverlappingRectangles_MergesIntoOne ()
  502. //{
  503. // var region = new Region (new Rectangle (0, 0, 10, 10));
  504. // region.MinimalUnion (new Rectangle (5, 0, 10, 10));
  505. // Assert.Single (region.GetRectangles ());
  506. // Assert.Equal (new Rectangle (0, 0, 15, 10), region.GetRectangles ().First ());
  507. //}
  508. //[Fact]
  509. //public void MinimalUnion_AdjacentRectangles_MergesIntoOne ()
  510. //{
  511. // var region = new Region (new Rectangle (0, 0, 10, 10));
  512. // region.MinimalUnion (new Rectangle (10, 0, 10, 10));
  513. // Assert.Single (region.GetRectangles ());
  514. // Assert.Equal (new Rectangle (0, 0, 20, 10), region.GetRectangles ().First ());
  515. //}
  516. //[Fact]
  517. //public void MinimalUnion_SeparateRectangles_KeepsBoth ()
  518. //{
  519. // var region = new Region (new Rectangle (0, 0, 10, 10));
  520. // region.MinimalUnion (new Rectangle (20, 20, 10, 10));
  521. // Assert.Equal (2, region.GetRectangles ().Length);
  522. // Assert.Contains (new Rectangle (0, 0, 10, 10), region.GetRectangles ());
  523. // Assert.Contains (new Rectangle (20, 20, 10, 10), region.GetRectangles ());
  524. //}
  525. //[Fact]
  526. //public void MinimalUnion_ComplexMerging_ProducesMinimalSet ()
  527. //{
  528. // var region = new Region (new Rectangle (0, 0, 10, 10));
  529. // region.MinimalUnion (new Rectangle (10, 0, 10, 10));
  530. // region.MinimalUnion (new Rectangle (0, 10, 10, 10));
  531. // region.MinimalUnion (new Rectangle (10, 10, 10, 10));
  532. // Assert.Single (region.GetRectangles ());
  533. // Assert.Equal (new Rectangle (0, 0, 20, 20), region.GetRectangles ().First ());
  534. //}
  535. [Fact]
  536. public void Intersect_ViewportLimitsDrawnRegion ()
  537. {
  538. // Arrange: Create regions for viewport and drawn content
  539. var viewport = new Region (new Rectangle (0, 0, 100, 100)); // Viewport
  540. var drawnRegion = new Region (new Rectangle (50, 50, 200, 200)); // Larger drawn content
  541. // Act: Intersect drawn region with viewport
  542. drawnRegion.Intersect (viewport);
  543. // Assert: Drawn region should be limited to viewport
  544. var rectangles = drawnRegion.GetRectangles ();
  545. Assert.Single (rectangles);
  546. Assert.Equal (new Rectangle (50, 50, 50, 50), rectangles [0]); // Limited to viewport bounds
  547. }
  548. //[Fact]
  549. //public void MinimalUnion_HorizontalMerge_MergesToSingleRectangle ()
  550. //{
  551. // // Arrange: Create a region with a rectangle at (0,0,5,5)
  552. // var region = new Region (new Rectangle (0, 0, 5, 5));
  553. // // Act: Merge an adjacent rectangle on the right using MinimalUnion
  554. // region.MinimalUnion (new Rectangle (5, 0, 5, 5));
  555. // var result = region.GetRectangles ();
  556. // // Assert: Expect a single merged rectangle covering (0,0,10,5)
  557. // Assert.Single (result);
  558. // Assert.Equal (new Rectangle (0, 0, 10, 5), result [0]);
  559. //}
  560. //[Fact]
  561. //public void MinimalUnion_VerticalMerge_MergesToSingleRectangle ()
  562. //{
  563. // // Arrange: Create a region with a rectangle at (0,0,5,5)
  564. // var region = new Region (new Rectangle (0, 0, 5, 5));
  565. // // Act: Merge an adjacent rectangle below using MinimalUnion
  566. // region.MinimalUnion (new Rectangle (0, 5, 5, 5));
  567. // var result = region.GetRectangles ();
  568. // // Assert: Expect a single merged rectangle covering (0,0,5,10)
  569. // Assert.Single (result);
  570. // Assert.Equal (new Rectangle (0, 0, 5, 10), result [0]);
  571. //}
  572. //[Fact]
  573. //public void MinimalUnion_OverlappingMerge_MergesToSingleRectangle ()
  574. //{
  575. // // Arrange: Create a region with a rectangle that overlaps with the next one horizontally
  576. // var region = new Region (new Rectangle (0, 0, 6, 5));
  577. // // Act: Merge an overlapping rectangle using MinimalUnion
  578. // region.MinimalUnion (new Rectangle (4, 0, 6, 5));
  579. // var result = region.GetRectangles ();
  580. // // Assert: Expect a single merged rectangle covering (0,0,10,5)
  581. // Assert.Single (result);
  582. // Assert.Equal (new Rectangle (0, 0, 10, 5), result [0]);
  583. //}
  584. //[Fact]
  585. //public void MinimalUnion_NonAdjacentRectangles_NoMergeOccurs ()
  586. //{
  587. // // Arrange: Create a region with one rectangle
  588. // var region = new Region (new Rectangle (0, 0, 5, 5));
  589. // // Act: Merge with a rectangle that does not touch the first
  590. // region.MinimalUnion (new Rectangle (6, 0, 5, 5));
  591. // var result = region.GetRectangles ();
  592. // // Assert: Expect two separate rectangles since they are not adjacent
  593. // Assert.Equal (2, result.Length);
  594. // Assert.Contains (new Rectangle (0, 0, 5, 5), result);
  595. // Assert.Contains (new Rectangle (6, 0, 5, 5), result);
  596. //}
  597. //[Fact]
  598. //public void MinimalUnion_MultipleMerge_FormsSingleContiguousRectangle ()
  599. //{
  600. // // Arrange: Four small rectangles that form a contiguous 6x6 block
  601. // var region = new Region (new Rectangle (0, 0, 3, 3));
  602. // // Act: Merge adjacent rectangles one by one using MinimalUnion
  603. // region.MinimalUnion (new Rectangle (3, 0, 3, 3)); // Now covers (0,0,6,3)
  604. // region.MinimalUnion (new Rectangle (0, 3, 3, 3)); // Add bottom-left
  605. // region.MinimalUnion (new Rectangle (3, 3, 3, 3)); // Add bottom-right to complete block
  606. // var result = region.GetRectangles ();
  607. // // Assert: Expect a single merged rectangle covering (0,0,6,6)
  608. // Assert.Single (result);
  609. // Assert.Equal (new Rectangle (0, 0, 6, 6), result [0]);
  610. //}
  611. [Fact]
  612. public void Translate_EmptyRegionAfterEmptyCombine_NoEffect ()
  613. {
  614. // Arrange: Create region and combine with empty rectangles
  615. var region = new Region ();
  616. region.Combine (new Rectangle (0, 0, 0, 0), RegionOp.Union); // Empty rectangle
  617. region.Combine (new Region (), RegionOp.Union); // Empty region
  618. // Act: Translate by (10, 20)
  619. region.Translate (10, 20);
  620. // Assert: Still empty
  621. Assert.Empty (region.GetRectangles ());
  622. }
  623. [Fact]
  624. public void Union_Rectangle_AddsToRegion ()
  625. {
  626. var region = new Region ();
  627. region.Union (new Rectangle (10, 10, 50, 50));
  628. Assert.False (region.IsEmpty ());
  629. Assert.True (region.Contains (20, 20));
  630. }
  631. [Fact]
  632. public void Union_Region_MergesRegions ()
  633. {
  634. var region1 = new Region (new (10, 10, 50, 50));
  635. var region2 = new Region (new (30, 30, 50, 50));
  636. region1.Union (region2);
  637. Assert.True (region1.Contains (20, 20));
  638. Assert.True (region1.Contains (40, 40));
  639. }
  640. [Fact]
  641. public void Union_Third_Rect_Covering_Two_Disjoint_Merges ()
  642. {
  643. var origRegion = new Region ();
  644. var region1 = new Region (new (0, 0, 1, 1));
  645. var region2 = new Region (new (1, 0, 1, 1));
  646. origRegion.Union (region1);
  647. origRegion.Union (region2);
  648. Assert.Equal (new Rectangle (0, 0, 2, 1), origRegion.GetBounds ());
  649. Assert.Equal (2, origRegion.GetRectangles ().Length);
  650. origRegion.Union (new Region (new (0, 0, 4, 1)));
  651. Assert.Equal (new Rectangle (0, 0, 4, 1), origRegion.GetBounds ());
  652. Assert.Equal (3, origRegion.GetRectangles ().Length);
  653. }
  654. [Fact]
  655. public void MinimalUnion_Third_Rect_Covering_Two_Disjoint_Merges ()
  656. {
  657. var origRegion = new Region ();
  658. var region1 = new Region (new (0, 0, 1, 1));
  659. var region2 = new Region (new (1, 0, 1, 1));
  660. origRegion.Union (region1);
  661. origRegion.Union (region2);
  662. Assert.Equal (new Rectangle (0, 0, 2, 1), origRegion.GetBounds ());
  663. Assert.Equal (2, origRegion.GetRectangles ().Length);
  664. origRegion.MinimalUnion (new Region (new (0, 0, 4, 1)));
  665. Assert.Equal (new Rectangle (0, 0, 4, 1), origRegion.GetBounds ());
  666. Assert.Single (origRegion.GetRectangles ());
  667. }
  668. /// <summary>
  669. /// Proves MergeRegion does not overly combine regions.
  670. /// </summary>
  671. [Fact]
  672. public void Union_Region_MergesRegions_NonOverlapping ()
  673. {
  674. // 012345
  675. // 0+++
  676. // 1+ +
  677. // 2+++
  678. // 3 ***
  679. // 4 * *
  680. // 5 ***
  681. var region1 = new Region (new (0, 0, 3, 3));
  682. var region2 = new Region (new (3, 3, 3, 3));
  683. region1.Union (region2);
  684. // Positive
  685. Assert.True (region1.Contains (0, 0));
  686. Assert.True (region1.Contains (1, 1));
  687. Assert.True (region1.Contains (2, 2));
  688. Assert.True (region1.Contains (4, 4));
  689. Assert.True (region1.Contains (5, 5));
  690. // Negative
  691. Assert.False (region1.Contains (0, 3));
  692. Assert.False (region1.Contains (3, 0));
  693. Assert.False (region1.Contains (6, 6));
  694. }
  695. /// <summary>
  696. /// Proves MergeRegion does not overly combine regions.
  697. /// </summary>
  698. [Fact]
  699. public void Union_Region_MergesRegions_Overlapping ()
  700. {
  701. // 01234567
  702. // 0+++++
  703. // 1+ +
  704. // 2+ +
  705. // 3+ *****
  706. // 4+++* *
  707. // 5 * *
  708. // 6 * *
  709. // 7 *****
  710. var region1 = new Region (new (0, 0, 5, 5));
  711. var region2 = new Region (new (3, 3, 5, 5));
  712. region1.Union (region2);
  713. // Positive
  714. Assert.True (region1.Contains (0, 0));
  715. Assert.True (region1.Contains (1, 1));
  716. Assert.True (region1.Contains (4, 4));
  717. Assert.True (region1.Contains (7, 7));
  718. // Negative
  719. Assert.False (region1.Contains (0, 5));
  720. Assert.False (region1.Contains (5, 0));
  721. Assert.False (region1.Contains (8, 8));
  722. Assert.False (region1.Contains (8, 8));
  723. }
  724. [Fact]
  725. public void Union_WithRectangle_AddsRectangle ()
  726. {
  727. var region = new Region ();
  728. var rect = new Rectangle (10, 10, 50, 50);
  729. region.Union (rect);
  730. Assert.True (region.Contains (20, 20));
  731. Assert.False (region.Contains (100, 100));
  732. }
  733. [Fact]
  734. public void Union_WithRegion_AddsRegion ()
  735. {
  736. var region1 = new Region (new (10, 10, 50, 50));
  737. var region2 = new Region (new (30, 30, 50, 50));
  738. region1.Union (region2.GetBounds ());
  739. Assert.True (region1.Contains (20, 20));
  740. Assert.True (region1.Contains (40, 40));
  741. }
  742. [Fact]
  743. public void Intersect_DeferredNormalization_PreservesSegments ()
  744. {
  745. var region = new Region (new (0, 0, 3, 1)); // Horizontal
  746. region.Union (new Rectangle (1, 0, 1, 2)); // Vertical
  747. region.Intersect (new Rectangle (0, 0, 2, 2)); // Clip
  748. Rectangle [] result = region.GetRectangles ();
  749. // Original & Updated (with normalization disabled) behavior:
  750. // Produces [(0,0,1,1), (1,0,1,2), (2,0,0,1)]
  751. Assert.Equal (3, result.Length);
  752. Assert.Contains (new (0, 0, 1, 1), result);
  753. Assert.Contains (new (1, 0, 1, 2), result);
  754. Assert.Contains (new (2, 0, 0, 1), result);
  755. }
  756. [Fact]
  757. public void MergeRectangles_Sort_Handles_Coincident_Events_Without_Crashing ()
  758. {
  759. // Arrange: Create rectangles designed to produce coincident start/end events
  760. // Rect1 ends at x=10. Rect2 and Rect3 start at x=10.
  761. // Rect4 ends at x=15. Rect5 starts at x=15.
  762. var rect1 = new Rectangle (0, 0, 10, 10); // Ends at x=10
  763. var rect2 = new Rectangle (10, 0, 10, 5); // Starts at x=10
  764. var rect3 = new Rectangle (10, 5, 10, 5); // Starts at x=10, adjacent to rect2 vertically
  765. var rect4 = new Rectangle (5, 10, 10, 5); // Ends at x=15
  766. var rect5 = new Rectangle (15, 10, 5, 5); // Starts at x=15
  767. var combinedList = new List<Rectangle> { rect1, rect2, rect3, rect4, rect5 };
  768. // Act & Assert:
  769. // The core assertion is that calling MergeRectangles with this list
  770. // does *not* throw the ArgumentException related to sorting.
  771. var exception = Record.Exception (() => Region.MergeRectangles (combinedList, false));
  772. // Assert
  773. Assert.Null (exception);
  774. // Optional secondary assertion: Check if the merge produced a reasonable number of rectangles
  775. // This isn't strictly necessary for proving the sort fix, but can be useful.
  776. // var merged = Region.MergeRectangles(combinedList, false);
  777. // Assert.True(merged.Count > 0 && merged.Count <= combinedList.Count);
  778. }
  779. [Fact]
  780. public void MergeRectangles_Sort_Handles_Multiple_Coincident_Starts ()
  781. {
  782. // Arrange: Multiple rectangles starting at the same X
  783. var rect1 = new Rectangle (5, 0, 10, 5);
  784. var rect2 = new Rectangle (5, 5, 10, 5);
  785. var rect3 = new Rectangle (5, 10, 10, 5);
  786. var combinedList = new List<Rectangle> { rect1, rect2, rect3 };
  787. // Act & Assert: Ensure no sorting exception
  788. var exception = Record.Exception (() => Region.MergeRectangles (combinedList, false));
  789. Assert.Null (exception);
  790. }
  791. [Fact]
  792. public void MergeRectangles_Sort_Handles_Multiple_Coincident_Ends ()
  793. {
  794. // Arrange: Multiple rectangles ending at the same X
  795. var rect1 = new Rectangle (0, 0, 10, 5);
  796. var rect2 = new Rectangle (0, 5, 10, 5);
  797. var rect3 = new Rectangle (0, 10, 10, 5);
  798. var combinedList = new List<Rectangle> { rect1, rect2, rect3 };
  799. // Act & Assert: Ensure no sorting exception
  800. var exception = Record.Exception (() => Region.MergeRectangles (combinedList, false));
  801. Assert.Null (exception);
  802. }
  803. [Fact]
  804. public void MergeRectangles_Sort_Handles_Coincident_Mixed_Events_Without_Crashing ()
  805. {
  806. // Arrange: Create rectangles specifically designed to produce multiple
  807. // Start AND End events at the same x-coordinate (e.g., x=10),
  808. // mimicking the pattern observed in the crash log.
  809. var rectA = new Rectangle (0, 0, 10, 5); // Ends at x=10, y=[0, 5)
  810. var rectB = new Rectangle (0, 10, 10, 5); // Ends at x=10, y=[10, 15)
  811. var rectC = new Rectangle (10, 0, 10, 5); // Starts at x=10, y=[0, 5)
  812. var rectD = new Rectangle (10, 10, 10, 5); // Starts at x=10, y=[10, 15)
  813. // Add another set at a different X to increase complexity
  814. var rectE = new Rectangle (5, 20, 10, 5); // Ends at x=15, y=[20, 25)
  815. var rectF = new Rectangle (5, 30, 10, 5); // Ends at x=15, y=[30, 35)
  816. var rectG = new Rectangle (15, 20, 10, 5); // Starts at x=15, y=[20, 25)
  817. var rectH = new Rectangle (15, 30, 10, 5); // Starts at x=15, y=[30, 35)
  818. // Add some unrelated rectangles
  819. var rectI = new Rectangle (0, 40, 5, 5);
  820. var rectJ = new Rectangle (100, 100, 5, 5);
  821. var combinedList = new List<Rectangle> {
  822. rectA, rectB, rectC, rectD,
  823. rectE, rectF, rectG, rectH,
  824. rectI, rectJ
  825. };
  826. // Act & Assert:
  827. // Call MergeRectangles with the current code.
  828. // This test *should* fail by throwing ArgumentException due to unstable sort.
  829. var exception = Record.Exception (() => Region.MergeRectangles (combinedList, false));
  830. // Assert that no exception was thrown (this assertion will fail with the current code)
  831. Assert.Null (exception);
  832. }
  833. [Fact]
  834. public void MergeRectangles_Sort_Reproduces_UICatalog_Crash_Pattern_Directly ()
  835. {
  836. // Arrange: Rectangles derived *directly* from the events list that caused the crash at x=67
  837. // This aims to replicate the exact problematic pattern.
  838. var rect_End_67_7_30 = new Rectangle (60, 7, 7, 23); // Ends at x=67, y=[7, 30) -> Event [33]
  839. var rect_Start_67_2_30 = new Rectangle (67, 2, 10, 28); // Starts at x=67, y=[2, 30) -> Event [34]
  840. var rect_Start_67_1_1 = new Rectangle (67, 1, 10, 0); // Starts at x=67, y=[1, 1) -> Event [49] (Height 0)
  841. var rect_End_67_1_1 = new Rectangle (60, 1, 7, 0); // Ends at x=67, y=[1, 1) -> Event [64] (Height 0)
  842. // Add rectangles for x=94/95 pattern
  843. var rect_End_94_1_30 = new Rectangle (90, 1, 4, 29); // Ends at x=94, y=[1, 30) -> Event [55]
  844. var rect_Start_94_1_1 = new Rectangle (94, 1, 10, 0); // Starts at x=94, y=[1, 1) -> Event [56]
  845. var rect_Start_94_7_30 = new Rectangle (94, 7, 10, 23); // Starts at x=94, y=[7, 30) -> Event [58]
  846. var rect_End_95_1_1 = new Rectangle (90, 1, 5, 0); // Ends at x=95, y=[1, 1) -> Event [57]
  847. var rect_End_95_7_30 = new Rectangle (90, 7, 5, 23); // Ends at x=95, y=[7, 30) -> Event [59]
  848. var rect_Start_95_0_30 = new Rectangle (95, 0, 10, 30); // Starts at x=95, y=[0, 30) -> Event [60]
  849. var combinedList = new List<Rectangle> {
  850. rect_End_67_7_30, rect_Start_67_2_30, rect_Start_67_1_1, rect_End_67_1_1,
  851. rect_End_94_1_30, rect_Start_94_1_1, rect_Start_94_7_30,
  852. rect_End_95_1_1, rect_End_95_7_30, rect_Start_95_0_30
  853. };
  854. // Act & Assert:
  855. // Call MergeRectangles. This test is specifically designed to fail with the current code.
  856. var exception = Record.Exception (() => Region.MergeRectangles (combinedList, false));
  857. // Assert that no exception was thrown (this assertion *should* fail with the current code)
  858. Assert.Null (exception);
  859. }
  860. [Fact]
  861. public void MergeRectangles_Sort_Reproduces_UICatalog_Crash_From_Captured_Data ()
  862. {
  863. // Arrange: The exact list of rectangles captured during the UICatalog crash
  864. var rectanglesFromCrash = new List<Rectangle> {
  865. new Rectangle(38, 7, 1, 11),
  866. new Rectangle(39, 7, 5, 23),
  867. new Rectangle(44, 7, 1, 23),
  868. new Rectangle(45, 7, 6, 23),
  869. new Rectangle(51, 7, 1, 23),
  870. new Rectangle(52, 7, 1, 23),
  871. new Rectangle(53, 7, 1, 23),
  872. new Rectangle(54, 7, 1, 23),
  873. new Rectangle(55, 7, 1, 23),
  874. new Rectangle(56, 7, 1, 23),
  875. new Rectangle(57, 7, 1, 23),
  876. new Rectangle(58, 7, 1, 23),
  877. new Rectangle(59, 7, 1, 23),
  878. new Rectangle(60, 7, 1, 23),
  879. new Rectangle(61, 7, 3, 23),
  880. new Rectangle(64, 7, 1, 23),
  881. new Rectangle(65, 7, 2, 23),
  882. new Rectangle(67, 2, 2, 28),
  883. new Rectangle(69, 2, 3, 28),
  884. new Rectangle(72, 2, 3, 28),
  885. new Rectangle(75, 2, 1, 28),
  886. new Rectangle(76, 2, 2, 28),
  887. new Rectangle(78, 2, 2, 28),
  888. new Rectangle(80, 7, 1, 23),
  889. new Rectangle(81, 1, 7, 29),
  890. new Rectangle(88, 1, 1, 29),
  891. new Rectangle(89, 1, 2, 29),
  892. new Rectangle(91, 1, 3, 29),
  893. new Rectangle(94, 1, 1, 0), // Note: Zero height
  894. new Rectangle(94, 7, 1, 23),
  895. new Rectangle(95, 0, 1, 30),
  896. new Rectangle(96, 0, 23, 30),
  897. new Rectangle(67, 1, 0, 0) // Note: Zero width and height
  898. };
  899. // Act & Assert:
  900. // Call MergeRectangles with the current code.
  901. // This test *should* fail by throwing ArgumentException due to unstable sort.
  902. var exception = Record.Exception (() => Region.MergeRectangles (rectanglesFromCrash, false));
  903. // Assert that no exception was thrown (this assertion will fail with the current code)
  904. Assert.Null (exception);
  905. }
  906. }