ViewDrawTextAndLineCanvasTests.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. using System.Text;
  2. using UnitTests;
  3. using Xunit.Abstractions;
  4. namespace ViewBaseTests.Drawing;
  5. public class ViewDrawTextAndLineCanvasTests () : FakeDriverBase
  6. {
  7. #region DrawText Tests
  8. [Fact]
  9. public void DrawText_EmptyText_DoesNotThrow ()
  10. {
  11. IDriver driver = CreateFakeDriver (80, 25);
  12. driver.Clip = new Region (driver.Screen);
  13. var view = new View
  14. {
  15. X = 10,
  16. Y = 10,
  17. Width = 20,
  18. Height = 20,
  19. Driver = driver,
  20. Text = ""
  21. };
  22. view.BeginInit ();
  23. view.EndInit ();
  24. view.LayoutSubViews ();
  25. var exception = Record.Exception (() => view.Draw ());
  26. Assert.Null (exception);
  27. }
  28. [Fact]
  29. public void DrawText_NullText_DoesNotThrow ()
  30. {
  31. IDriver driver = CreateFakeDriver (80, 25);
  32. driver.Clip = new Region (driver.Screen);
  33. var view = new View
  34. {
  35. X = 10,
  36. Y = 10,
  37. Width = 20,
  38. Height = 20,
  39. Driver = driver,
  40. Text = null!
  41. };
  42. view.BeginInit ();
  43. view.EndInit ();
  44. view.LayoutSubViews ();
  45. var exception = Record.Exception (() => view.Draw ());
  46. Assert.Null (exception);
  47. }
  48. [Fact]
  49. public void DrawText_DrawsTextToDriver ()
  50. {
  51. IDriver driver = CreateFakeDriver (80, 25);
  52. driver.Clip = new Region (driver.Screen);
  53. var view = new View
  54. {
  55. X = 1,
  56. Y = 1,
  57. Width = 20,
  58. Height = 20,
  59. Driver = driver,
  60. Text = "Test"
  61. };
  62. view.BeginInit ();
  63. view.EndInit ();
  64. view.LayoutSubViews ();
  65. view.Draw ();
  66. // Text should appear at the content location
  67. Point screenPos = view.ContentToScreen (Point.Empty);
  68. Assert.Equal ("T", driver.Contents! [screenPos.Y, screenPos.X].Grapheme);
  69. Assert.Equal ("e", driver.Contents [screenPos.Y, screenPos.X + 1].Grapheme);
  70. Assert.Equal ("s", driver.Contents [screenPos.Y, screenPos.X + 2].Grapheme);
  71. Assert.Equal ("t", driver.Contents [screenPos.Y, screenPos.X + 3].Grapheme);
  72. }
  73. [Fact]
  74. public void DrawText_WithFocus_UsesFocusAttribute ()
  75. {
  76. IDriver driver = CreateFakeDriver (80, 25);
  77. driver.Clip = new Region (driver.Screen);
  78. var view = new View
  79. {
  80. X = 10,
  81. Y = 10,
  82. Width = 20,
  83. Height = 20,
  84. Driver = driver,
  85. Text = "Test",
  86. CanFocus = true
  87. };
  88. view.BeginInit ();
  89. view.EndInit ();
  90. view.LayoutSubViews ();
  91. view.SetFocus ();
  92. view.Draw ();
  93. // Text should use focus attribute
  94. Point screenPos = view.ContentToScreen (Point.Empty);
  95. Attribute expectedAttr = view.GetAttributeForRole (VisualRole.Focus);
  96. Assert.Equal (expectedAttr, driver.Contents! [screenPos.Y, screenPos.X].Attribute);
  97. }
  98. [Fact]
  99. public void DrawText_WithoutFocus_UsesNormalAttribute ()
  100. {
  101. IDriver driver = CreateFakeDriver (80, 25);
  102. driver.Clip = new Region (driver.Screen);
  103. var view = new View
  104. {
  105. X = 10,
  106. Y = 10,
  107. Width = 20,
  108. Height = 20,
  109. Driver = driver,
  110. Text = "Test",
  111. CanFocus = true
  112. };
  113. view.BeginInit ();
  114. view.EndInit ();
  115. view.LayoutSubViews ();
  116. view.Draw ();
  117. // Text should use normal attribute
  118. Point screenPos = view.ContentToScreen (Point.Empty);
  119. Attribute expectedAttr = view.GetAttributeForRole (VisualRole.Normal);
  120. Assert.Equal (expectedAttr, driver.Contents! [screenPos.Y, screenPos.X].Attribute);
  121. }
  122. [Fact]
  123. public void DrawText_SetsSubViewNeedsDraw ()
  124. {
  125. IDriver driver = CreateFakeDriver (80, 25);
  126. driver.Clip = new Region (driver.Screen);
  127. var view = new View
  128. {
  129. X = 10,
  130. Y = 10,
  131. Width = 20,
  132. Height = 20,
  133. Driver = driver,
  134. Text = "Test"
  135. };
  136. var child = new View { X = 0, Y = 0, Width = 10, Height = 10 };
  137. view.Add (child);
  138. view.BeginInit ();
  139. view.EndInit ();
  140. view.LayoutSubViews ();
  141. // Clear SubViewNeedsDraw
  142. view.Draw ();
  143. Assert.False (view.SubViewNeedsDraw);
  144. // Call DrawText directly which should set SubViewNeedsDraw
  145. view.DrawText ();
  146. // SubViews need to be redrawn since text was drawn over them
  147. Assert.True (view.SubViewNeedsDraw);
  148. }
  149. [Fact]
  150. public void DrawingText_Event_Raised ()
  151. {
  152. IDriver driver = CreateFakeDriver (80, 25);
  153. driver.Clip = new Region (driver.Screen);
  154. bool eventRaised = false;
  155. var view = new View
  156. {
  157. X = 10,
  158. Y = 10,
  159. Width = 20,
  160. Height = 20,
  161. Driver = driver,
  162. Text = "Test"
  163. };
  164. view.BeginInit ();
  165. view.EndInit ();
  166. view.LayoutSubViews ();
  167. view.DrawingText += (s, e) => eventRaised = true;
  168. view.Draw ();
  169. Assert.True (eventRaised);
  170. }
  171. #endregion
  172. #region LineCanvas Tests
  173. [Fact]
  174. public void LineCanvas_InitiallyEmpty ()
  175. {
  176. var view = new View ();
  177. Assert.NotNull (view.LineCanvas);
  178. Assert.Equal (Rectangle.Empty, view.LineCanvas.Bounds);
  179. }
  180. [Fact]
  181. public void RenderLineCanvas_DrawsLines ()
  182. {
  183. IDriver driver = CreateFakeDriver (80, 25);
  184. driver.Clip = new Region (driver.Screen);
  185. var view = new View
  186. {
  187. X = 10,
  188. Y = 10,
  189. Width = 20,
  190. Height = 20,
  191. Driver = driver
  192. };
  193. view.BeginInit ();
  194. view.EndInit ();
  195. view.LayoutSubViews ();
  196. // Add a line to the canvas
  197. Point screenPos = new Point (15, 15);
  198. view.LineCanvas.AddLine (screenPos, 5, Orientation.Horizontal, LineStyle.Single);
  199. view.RenderLineCanvas (null);
  200. // Verify the line was drawn (check for horizontal line character)
  201. for (int i = 0; i < 5; i++)
  202. {
  203. Assert.NotEqual (" ", driver.Contents! [screenPos.Y, screenPos.X + i].Grapheme);
  204. }
  205. }
  206. [Fact]
  207. public void RenderLineCanvas_ClearsAfterRendering ()
  208. {
  209. IDriver driver = CreateFakeDriver (80, 25);
  210. driver.Clip = new Region (driver.Screen);
  211. var view = new View
  212. {
  213. X = 10,
  214. Y = 10,
  215. Width = 20,
  216. Height = 20,
  217. Driver = driver
  218. };
  219. view.BeginInit ();
  220. view.EndInit ();
  221. view.LayoutSubViews ();
  222. // Add a line to the canvas
  223. view.LineCanvas.AddLine (new Point (15, 15), 5, Orientation.Horizontal, LineStyle.Single);
  224. Assert.NotEqual (Rectangle.Empty, view.LineCanvas.Bounds);
  225. view.RenderLineCanvas (null);
  226. // LineCanvas should be cleared after rendering
  227. Assert.Equal (Rectangle.Empty, view.LineCanvas.Bounds);
  228. }
  229. [Fact]
  230. public void RenderLineCanvas_WithSuperViewRendersLineCanvas_DoesNotClear ()
  231. {
  232. IDriver driver = CreateFakeDriver (80, 25);
  233. driver.Clip = new Region (driver.Screen);
  234. var view = new View
  235. {
  236. X = 10,
  237. Y = 10,
  238. Width = 20,
  239. Height = 20,
  240. Driver = driver,
  241. SuperViewRendersLineCanvas = true
  242. };
  243. view.BeginInit ();
  244. view.EndInit ();
  245. view.LayoutSubViews ();
  246. // Add a line to the canvas
  247. view.LineCanvas.AddLine (new Point (15, 15), 5, Orientation.Horizontal, LineStyle.Single);
  248. Rectangle boundsBefore = view.LineCanvas.Bounds;
  249. view.RenderLineCanvas (null);
  250. // LineCanvas should NOT be cleared when SuperViewRendersLineCanvas is true
  251. Assert.Equal (boundsBefore, view.LineCanvas.Bounds);
  252. }
  253. [Fact]
  254. public void SuperViewRendersLineCanvas_MergesWithParentCanvas ()
  255. {
  256. IDriver driver = CreateFakeDriver (80, 25);
  257. driver.Clip = new Region (driver.Screen);
  258. var parent = new View
  259. {
  260. X = 10,
  261. Y = 10,
  262. Width = 50,
  263. Height = 50,
  264. Driver = driver
  265. };
  266. var child = new View
  267. {
  268. X = 5,
  269. Y = 5,
  270. Width = 30,
  271. Height = 30,
  272. SuperViewRendersLineCanvas = true
  273. };
  274. parent.Add (child);
  275. parent.BeginInit ();
  276. parent.EndInit ();
  277. parent.LayoutSubViews ();
  278. // Add a line to child's canvas
  279. child.LineCanvas.AddLine (new Point (20, 20), 5, Orientation.Horizontal, LineStyle.Single);
  280. Assert.NotEqual (Rectangle.Empty, child.LineCanvas.Bounds);
  281. Assert.Equal (Rectangle.Empty, parent.LineCanvas.Bounds);
  282. parent.Draw ();
  283. // Child's canvas should have been merged into parent's
  284. // and child's canvas should be cleared
  285. Assert.Equal (Rectangle.Empty, child.LineCanvas.Bounds);
  286. }
  287. [Fact]
  288. public void OnRenderingLineCanvas_CanPreventRendering ()
  289. {
  290. IDriver driver = CreateFakeDriver (80, 25);
  291. driver.Clip = new Region (driver.Screen);
  292. var view = new TestView
  293. {
  294. X = 10,
  295. Y = 10,
  296. Width = 20,
  297. Height = 20,
  298. Driver = driver,
  299. PreventRenderLineCanvas = true
  300. };
  301. view.BeginInit ();
  302. view.EndInit ();
  303. view.LayoutSubViews ();
  304. // Add a line to the canvas
  305. Point screenPos = new Point (15, 15);
  306. view.LineCanvas.AddLine (screenPos, 5, Orientation.Horizontal, LineStyle.Single);
  307. view.Draw ();
  308. // When OnRenderingLineCanvas returns true, RenderLineCanvas is not called
  309. // So the LineCanvas should still have lines (not cleared)
  310. // BUT because SuperViewRendersLineCanvas is false (default), the LineCanvas
  311. // gets cleared during the draw cycle anyway. We need to check that the
  312. // line was NOT actually rendered to the driver.
  313. bool lineRendered = true;
  314. for (int i = 0; i < 5; i++)
  315. {
  316. if (driver.Contents! [screenPos.Y, screenPos.X + i].Grapheme == " ")
  317. {
  318. lineRendered = false;
  319. break;
  320. }
  321. }
  322. Assert.False (lineRendered);
  323. }
  324. #endregion
  325. #region SuperViewRendersLineCanvas Tests
  326. [Fact]
  327. public void SuperViewRendersLineCanvas_DefaultFalse ()
  328. {
  329. var view = new View ();
  330. Assert.False (view.SuperViewRendersLineCanvas);
  331. }
  332. [Fact]
  333. public void SuperViewRendersLineCanvas_CanBeSet ()
  334. {
  335. var view = new View { SuperViewRendersLineCanvas = true };
  336. Assert.True (view.SuperViewRendersLineCanvas);
  337. }
  338. [Fact]
  339. public void Draw_WithSuperViewRendersLineCanvas_SetsNeedsDraw ()
  340. {
  341. IDriver driver = CreateFakeDriver (80, 25);
  342. driver.Clip = new Region (driver.Screen);
  343. var parent = new View
  344. {
  345. X = 10,
  346. Y = 10,
  347. Width = 50,
  348. Height = 50,
  349. Driver = driver
  350. };
  351. var child = new View
  352. {
  353. X = 5,
  354. Y = 5,
  355. Width = 30,
  356. Height = 30,
  357. SuperViewRendersLineCanvas = true
  358. };
  359. parent.Add (child);
  360. parent.BeginInit ();
  361. parent.EndInit ();
  362. parent.LayoutSubViews ();
  363. // Draw once to clear NeedsDraw
  364. parent.Draw ();
  365. Assert.False (child.NeedsDraw);
  366. // Draw again - child with SuperViewRendersLineCanvas should be redrawn
  367. parent.Draw ();
  368. // The child should have been set to NeedsDraw during DrawSubViews
  369. // This is verified by the fact that it was drawn (we can't check NeedsDraw after Draw)
  370. }
  371. #endregion
  372. #region Helper Test View
  373. private class TestView : View
  374. {
  375. public bool PreventRenderLineCanvas { get; set; }
  376. protected override bool OnRenderingLineCanvas ()
  377. {
  378. return PreventRenderLineCanvas || base.OnRenderingLineCanvas ();
  379. }
  380. }
  381. #endregion
  382. }