DrawOuterBoundaryTests.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. using System.Collections.Concurrent;
  2. using Xunit.Abstractions;
  3. namespace UnitTests_Parallelizable.DrawingTests;
  4. /// <summary>
  5. /// Tests for <see cref="Region.DrawOuterBoundary"/>.
  6. /// </summary>
  7. public class DrawOuterBoundaryTests (ITestOutputHelper output)
  8. {
  9. private readonly ITestOutputHelper _output = output;
  10. [Fact]
  11. public void DrawOuterBoundary_AfterIntersect_DrawsIntersectedBoundary ()
  12. {
  13. // Arrange
  14. var region = new Region (new (0, 0, 10, 10));
  15. region.Intersect (new Rectangle (5, 5, 10, 10));
  16. var canvas = new LineCanvas ();
  17. // Act
  18. region.DrawOuterBoundary (canvas, LineStyle.Single);
  19. // Assert
  20. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  21. Assert.NotEmpty (cells);
  22. }
  23. [Fact]
  24. public void DrawOuterBoundary_AfterMinimalUnion_DrawsMinimalBoundary ()
  25. {
  26. // Arrange
  27. var region = new Region (new (0, 0, 3, 3));
  28. region.MinimalUnion (new Rectangle (3, 0, 3, 3));
  29. var canvas = new LineCanvas ();
  30. // Act
  31. region.DrawOuterBoundary (canvas, LineStyle.Single);
  32. // Assert
  33. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  34. Assert.NotEmpty (cells);
  35. }
  36. [Fact]
  37. public void DrawOuterBoundary_ComplexShape_HandlesMultipleRegions ()
  38. {
  39. // Arrange - Create a complex shape with multiple rectangles
  40. var region = new Region (new (0, 0, 3, 3));
  41. region.Union (new Rectangle (3, 3, 3, 3));
  42. region.Union (new Rectangle (6, 0, 3, 3));
  43. var canvas = new LineCanvas ();
  44. // Act
  45. region.DrawOuterBoundary (canvas, LineStyle.Single);
  46. // Assert
  47. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  48. Assert.NotEmpty (cells);
  49. }
  50. [Fact]
  51. public void DrawOuterBoundary_DiagonallyConnectedRectangles_DrawsOuterBoundary ()
  52. {
  53. // Arrange - Test the specific case from BUGBUG comment: (0,0,3,3) and (3,3,3,3)
  54. var region = new Region (new (0, 0, 3, 3));
  55. region.Union (new Rectangle (3, 3, 3, 3));
  56. var canvas = new LineCanvas ();
  57. // Act
  58. region.DrawOuterBoundary (canvas, LineStyle.Single);
  59. // Assert
  60. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  61. Assert.NotEmpty (cells);
  62. // Note: According to BUGBUG comment, this should draw specific shape
  63. // with connecting corner but currently draws incorrectly
  64. }
  65. [Fact]
  66. public void DrawOuterBoundary_EmptyRegion_DoesNotThrow ()
  67. {
  68. // Arrange
  69. var region = new Region ();
  70. var canvas = new LineCanvas ();
  71. // Act
  72. Exception exception = Record.Exception (() => region.DrawOuterBoundary (canvas, LineStyle.Single));
  73. // Assert
  74. Assert.Null (exception);
  75. Assert.Empty (canvas.GetCellMap ());
  76. }
  77. [Fact]
  78. public void DrawOuterBoundary_GridPattern_DrawsOuterBoundary ()
  79. {
  80. // Arrange - Create a checkerboard pattern
  81. var region = new Region (new (0, 0, 2, 2));
  82. region.Union (new Rectangle (4, 0, 2, 2));
  83. region.Union (new Rectangle (0, 4, 2, 2));
  84. region.Union (new Rectangle (4, 4, 2, 2));
  85. var canvas = new LineCanvas ();
  86. // Act
  87. region.DrawOuterBoundary (canvas, LineStyle.Single);
  88. // Assert
  89. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  90. Assert.NotEmpty (cells);
  91. }
  92. [Fact]
  93. public void DrawOuterBoundary_HollowRectangle_DrawsOuterAndInnerBoundaries ()
  94. {
  95. // Arrange - Create a hollow rectangle (outer rect with inner rect removed)
  96. var region = new Region (new (0, 0, 10, 10));
  97. region.Exclude (new Rectangle (2, 2, 6, 6));
  98. var canvas = new LineCanvas ();
  99. // Act
  100. region.DrawOuterBoundary (canvas, LineStyle.Single);
  101. // Assert
  102. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  103. Assert.NotEmpty (cells);
  104. }
  105. [Fact]
  106. public void DrawOuterBoundary_HorizontalLineRectangle_DrawsHorizontalLine ()
  107. {
  108. // Arrange - A horizontal line (width>1, height=1)
  109. var region = new Region (new (0, 0, 4, 1));
  110. var canvas = new LineCanvas ();
  111. // Act
  112. region.DrawOuterBoundary (canvas, LineStyle.Single);
  113. // Assert
  114. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  115. Assert.NotEmpty (cells);
  116. }
  117. [Fact]
  118. public void DrawOuterBoundary_LShapedRegion_DrawsLShapeBoundary ()
  119. {
  120. // Arrange - Create an L-shape
  121. var region = new Region (new (0, 0, 3, 3));
  122. region.Union (new Rectangle (0, 3, 3, 3));
  123. var canvas = new LineCanvas ();
  124. // Act
  125. region.DrawOuterBoundary (canvas, LineStyle.Single);
  126. // Assert
  127. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  128. Assert.NotEmpty (cells);
  129. }
  130. [Fact]
  131. public void DrawOuterBoundary_MultipleCallsOnSameCanvas_AccumulatesLines ()
  132. {
  133. // Arrange
  134. var region1 = new Region (new (0, 0, 3, 3));
  135. var region2 = new Region (new (5, 5, 3, 3));
  136. var canvas = new LineCanvas ();
  137. // Act
  138. region1.DrawOuterBoundary (canvas, LineStyle.Single);
  139. int cellCountAfterFirst = canvas.GetCellMap ().Count;
  140. region2.DrawOuterBoundary (canvas, LineStyle.Single);
  141. int cellCountAfterSecond = canvas.GetCellMap ().Count;
  142. // Assert
  143. Assert.True (cellCountAfterSecond >= cellCountAfterFirst);
  144. }
  145. [Fact]
  146. public void DrawOuterBoundary_MultipleRegionsWithGaps_DrawsSeparateBoundaries ()
  147. {
  148. // Arrange
  149. var region = new Region ();
  150. region.Union (new Rectangle (0, 0, 2, 2));
  151. region.Union (new Rectangle (4, 0, 2, 2));
  152. region.Union (new Rectangle (8, 0, 2, 2));
  153. var canvas = new LineCanvas ();
  154. // Act
  155. region.DrawOuterBoundary (canvas, LineStyle.Single);
  156. // Assert
  157. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  158. Assert.NotEmpty (cells);
  159. // Should have three separate boundary regions
  160. }
  161. [Fact]
  162. public void DrawOuterBoundary_OverlappingRectangles_DrawsOuterBoundaryOnly ()
  163. {
  164. // Arrange - Two overlapping rectangles
  165. var region = new Region (new (0, 0, 5, 5));
  166. region.Union (new Rectangle (3, 3, 5, 5));
  167. var canvas = new LineCanvas ();
  168. // Act
  169. region.DrawOuterBoundary (canvas, LineStyle.Single);
  170. // Assert
  171. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  172. Assert.NotEmpty (cells);
  173. // Should only draw outer perimeter, not the overlapping internal area
  174. }
  175. [Fact]
  176. public void DrawOuterBoundary_RectangleAtNegativeCoordinates_DrawsBoundary ()
  177. {
  178. // Arrange
  179. var region = new Region (new (-5, -5, 3, 3));
  180. var canvas = new LineCanvas ();
  181. // Act
  182. region.DrawOuterBoundary (canvas, LineStyle.Single);
  183. // Assert
  184. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  185. Assert.NotEmpty (cells);
  186. }
  187. [Fact]
  188. public void DrawOuterBoundary_SinglePixelRectangle_DrawsSinglePoint ()
  189. {
  190. // Arrange - A 1x1 rectangle
  191. var region = new Region (new (5, 5, 1, 1));
  192. var canvas = new LineCanvas ();
  193. // Act
  194. region.DrawOuterBoundary (canvas, LineStyle.Single);
  195. // Assert
  196. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  197. Assert.NotEmpty (cells);
  198. }
  199. [Fact]
  200. public void DrawOuterBoundary_SingleRectangle_DrawsBoundary ()
  201. {
  202. // Arrange
  203. var region = new Region (new (0, 0, 3, 3));
  204. var canvas = new LineCanvas ();
  205. // Act
  206. region.DrawOuterBoundary (canvas, LineStyle.Single);
  207. // Assert
  208. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  209. Assert.NotEmpty (cells);
  210. }
  211. [Fact]
  212. public void DrawOuterBoundary_SingleWidthRegion_DrawsCorrectly ()
  213. {
  214. // Arrange - Test the specific case mentioned in BUGBUG comment
  215. var region = new Region (new (0, 0, 1, 4));
  216. var canvas = new LineCanvas ();
  217. // Act
  218. region.DrawOuterBoundary (canvas, LineStyle.Single);
  219. // Assert
  220. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  221. Assert.NotEmpty (cells);
  222. // Note: According to BUGBUG, this should draw a single vertical line
  223. // but currently draws too wide
  224. }
  225. [Fact]
  226. public void DrawOuterBoundary_ThreadSafety_MultipleThreadsDrawing ()
  227. {
  228. // Arrange
  229. var region = new Region (new (0, 0, 10, 10));
  230. ConcurrentBag<Exception> exceptions = new ();
  231. // Act
  232. Parallel.For (
  233. 0,
  234. 10,
  235. i =>
  236. {
  237. try
  238. {
  239. var canvas = new LineCanvas ();
  240. region.DrawOuterBoundary (canvas, LineStyle.Single);
  241. }
  242. catch (Exception ex)
  243. {
  244. exceptions.Add (ex);
  245. }
  246. });
  247. // Assert
  248. Assert.Empty (exceptions);
  249. }
  250. [Fact]
  251. public void DrawOuterBoundary_ThreeWidthRegion_DrawsCorrectly ()
  252. {
  253. // Arrange - Test the specific case mentioned in BUGBUG comment
  254. var region = new Region (new (20, 0, 3, 4));
  255. var canvas = new LineCanvas ();
  256. // Act
  257. region.DrawOuterBoundary (canvas, LineStyle.Single);
  258. // Assert
  259. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  260. Assert.NotEmpty (cells);
  261. }
  262. [Fact]
  263. public void DrawOuterBoundary_TShapedRegion_DrawsCorrectBoundary ()
  264. {
  265. // Arrange - Create a T-shape
  266. var region = new Region (new (0, 0, 9, 3)); // Horizontal bar
  267. region.Union (new Rectangle (3, 3, 3, 6)); // Vertical bar
  268. var canvas = new LineCanvas ();
  269. // Act
  270. region.DrawOuterBoundary (canvas, LineStyle.Single);
  271. // Assert
  272. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  273. Assert.NotEmpty (cells);
  274. }
  275. [Fact]
  276. public void DrawOuterBoundary_TwoAdjacentRectangles_DrawsOuterPerimeter ()
  277. {
  278. // Arrange - Two rectangles side by side
  279. var region = new Region (new (0, 0, 3, 3));
  280. region.Union (new Rectangle (3, 0, 3, 3));
  281. var canvas = new LineCanvas ();
  282. // Act
  283. region.DrawOuterBoundary (canvas, LineStyle.Single);
  284. // Assert
  285. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  286. Assert.NotEmpty (cells);
  287. // Should draw outer boundary, not internal dividing line
  288. // The combined region should be treated as one shape
  289. }
  290. [Fact]
  291. public void DrawOuterBoundary_TwoSeparateRectangles_DrawsTwoBoundaries ()
  292. {
  293. // Arrange - Two non-adjacent rectangles
  294. var region = new Region (new (0, 0, 2, 2));
  295. region.Union (new Rectangle (5, 5, 2, 2));
  296. var canvas = new LineCanvas ();
  297. // Act
  298. region.DrawOuterBoundary (canvas, LineStyle.Single);
  299. // Assert
  300. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  301. Assert.NotEmpty (cells);
  302. // Should have boundaries for both rectangles
  303. Assert.True (cells.Count > 0);
  304. }
  305. [Fact]
  306. public void DrawOuterBoundary_TwoWidthRegion_DrawsCorrectly ()
  307. {
  308. // Arrange - Test the specific case mentioned in BUGBUG comment
  309. var region = new Region (new (10, 0, 2, 4));
  310. var canvas = new LineCanvas ();
  311. // Act
  312. region.DrawOuterBoundary (canvas, LineStyle.Single);
  313. // Assert
  314. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  315. Assert.NotEmpty (cells);
  316. }
  317. [Theory]
  318. [InlineData (0, 0)]
  319. [InlineData (10, 10)]
  320. [InlineData (-5, -5)]
  321. [InlineData (100, 100)]
  322. public void DrawOuterBoundary_VariousPositions_DrawsBoundary (int x, int y)
  323. {
  324. // Arrange
  325. var region = new Region (new (x, y, 5, 5));
  326. var canvas = new LineCanvas ();
  327. // Act
  328. Exception exception = Record.Exception (() => region.DrawOuterBoundary (canvas, LineStyle.Single));
  329. // Assert
  330. Assert.Null (exception);
  331. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  332. Assert.NotEmpty (cells);
  333. }
  334. [Theory]
  335. [InlineData (1, 1)]
  336. [InlineData (1, 5)]
  337. [InlineData (5, 1)]
  338. [InlineData (2, 2)]
  339. [InlineData (10, 10)]
  340. [InlineData (100, 100)]
  341. public void DrawOuterBoundary_VariousSizes_DrawsBoundary (int width, int height)
  342. {
  343. // Arrange
  344. var region = new Region (new (0, 0, width, height));
  345. var canvas = new LineCanvas ();
  346. // Act
  347. Exception exception = Record.Exception (() => region.DrawOuterBoundary (canvas, LineStyle.Single));
  348. // Assert
  349. Assert.Null (exception);
  350. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  351. Assert.NotEmpty (cells);
  352. }
  353. [Fact]
  354. public void DrawOuterBoundary_VerticalLineRectangle_DrawsVerticalLine ()
  355. {
  356. // Arrange - A vertical line (width=1, height>1)
  357. var region = new Region (new (0, 0, 1, 4));
  358. var canvas = new LineCanvas ();
  359. // Act
  360. region.DrawOuterBoundary (canvas, LineStyle.Single);
  361. // Assert
  362. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  363. Assert.NotEmpty (cells);
  364. }
  365. [Fact]
  366. public void DrawOuterBoundary_VeryLargeRegion_FallsBackToDrawBoundaries ()
  367. {
  368. // Arrange - Create a region larger than the 1000x1000 threshold
  369. var region = new Region (new (0, 0, 1100, 1100));
  370. var canvas = new LineCanvas ();
  371. // Act
  372. Exception exception = Record.Exception (() => region.DrawOuterBoundary (canvas, LineStyle.Single));
  373. // Assert
  374. Assert.Null (exception);
  375. // Should fall back to DrawBoundaries for very large regions
  376. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  377. Assert.NotEmpty (cells);
  378. }
  379. [Fact]
  380. public void DrawOuterBoundary_WithCustomAttribute_AppliesAttribute ()
  381. {
  382. // Arrange
  383. var region = new Region (new (0, 0, 3, 3));
  384. var canvas = new LineCanvas ();
  385. var attribute = new Attribute (Color.Red, Color.Blue);
  386. // Act
  387. region.DrawOuterBoundary (canvas, LineStyle.Single, attribute);
  388. // Assert
  389. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  390. Assert.NotEmpty (cells);
  391. }
  392. [Fact]
  393. public void DrawOuterBoundary_WithDifferentLineStyles_DrawsWithCorrectStyle ()
  394. {
  395. // Arrange
  396. var region = new Region (new (0, 0, 3, 3));
  397. // Test each line style
  398. foreach (LineStyle style in Enum.GetValues<LineStyle> ())
  399. {
  400. var canvas = new LineCanvas ();
  401. // Act
  402. region.DrawOuterBoundary (canvas, style);
  403. // Assert
  404. Dictionary<Point, Cell?> cells = canvas.GetCellMap ();
  405. Assert.NotEmpty (cells);
  406. }
  407. }
  408. [Fact]
  409. public void DrawOuterBoundary_ZeroHeightRectangle_HandlesGracefully ()
  410. {
  411. // Arrange - Rectangle with zero height
  412. var region = new Region (new (5, 5, 5, 0));
  413. var canvas = new LineCanvas ();
  414. // Act
  415. Exception exception = Record.Exception (() => region.DrawOuterBoundary (canvas, LineStyle.Single));
  416. // Assert
  417. Assert.Null (exception);
  418. }
  419. [Fact]
  420. public void DrawOuterBoundary_ZeroWidthRectangle_HandlesGracefully ()
  421. {
  422. // Arrange - Rectangle with zero width
  423. var region = new Region (new (5, 5, 0, 5));
  424. var canvas = new LineCanvas ();
  425. // Act
  426. Exception exception = Record.Exception (() => region.DrawOuterBoundary (canvas, LineStyle.Single));
  427. // Assert
  428. Assert.Null (exception);
  429. }
  430. }