StaticDrawTests.cs 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. #nullable enable
  2. using UnitTests;
  3. namespace ViewBaseTests.Drawing;
  4. /// <summary>
  5. /// Tests for the static View.Draw(IEnumerable&lt;View&gt;, bool) method
  6. /// </summary>
  7. [Trait ("Category", "Output")]
  8. public class StaticDrawTests : FakeDriverBase
  9. {
  10. [Fact]
  11. public void StaticDraw_ClearsSubViewNeedsDraw_AfterMarginDrawMargins ()
  12. {
  13. // This test validates the fix where the static Draw method calls ClearNeedsDraw()
  14. // on all peer views after drawing them AND after calling Margin.DrawMargins().
  15. //
  16. // THE BUG (before the fix):
  17. // Margin.DrawMargins() can cause SubViewNeedsDraw to be set on views in the hierarchy.
  18. // This would leave SubViewNeedsDraw = true even after drawing completed.
  19. //
  20. // THE FIX (current code):
  21. // The static Draw() method explicitly calls ClearNeedsDraw() on all peer views
  22. // at the very end, AFTER Margin.DrawMargins(), clearing any SubViewNeedsDraw flags
  23. // that were set during margin drawing.
  24. IDriver driver = CreateFakeDriver ();
  25. driver.Clip = new (driver.Screen);
  26. // Create a view hierarchy where a subview's subview has a margin
  27. // This reproduces the scenario where Margin.DrawMargins sets SubViewNeedsDraw
  28. View superview = new ()
  29. {
  30. X = 0,
  31. Y = 0,
  32. Width = 60,
  33. Height = 60,
  34. Driver = driver,
  35. Id = "SuperView"
  36. };
  37. View subview1 = new () { X = 0, Y = 0, Width = 40, Height = 40, Id = "SubView1" };
  38. View subview2 = new () { X = 0, Y = 20, Width = 20, Height = 20, Id = "SubView2" };
  39. // Add a subview to subview1 that has a margin with shadow
  40. // This is key to reproducing the bug
  41. View subSubView = new ()
  42. {
  43. X = 5,
  44. Y = 5,
  45. Width = 20,
  46. Height = 20,
  47. Id = "SubSubView"
  48. };
  49. subSubView.Margin!.Thickness = new (1);
  50. subSubView.Margin.ShadowStyle = ShadowStyle.Transparent;
  51. subview1.Add (subSubView);
  52. superview.Add (subview1, subview2);
  53. superview.BeginInit ();
  54. superview.EndInit ();
  55. superview.LayoutSubViews ();
  56. // All views initially need drawing
  57. Assert.True (superview.NeedsDraw);
  58. Assert.True (superview.SubViewNeedsDraw);
  59. Assert.True (subview1.NeedsDraw);
  60. Assert.True (subview1.SubViewNeedsDraw);
  61. Assert.True (subview2.NeedsDraw);
  62. Assert.True (subSubView.NeedsDraw);
  63. Assert.True (subSubView.Margin.NeedsDraw);
  64. // Call the static Draw method on the subviews
  65. // This will:
  66. // 1. Call view.Draw() on each subview
  67. // 2. Call Margin.DrawMargins() which may set SubViewNeedsDraw in the hierarchy
  68. // 3. Call ClearNeedsDraw() on each subview to clean up
  69. View.Draw (superview.InternalSubViews, force: false);
  70. // After the static Draw completes:
  71. // All subviews should have NeedsDraw = false
  72. Assert.False (subview1.NeedsDraw, "SubView1 should not need drawing after Draw()");
  73. Assert.False (subview2.NeedsDraw, "SubView2 should not need drawing after Draw()");
  74. Assert.False (subSubView.NeedsDraw, "SubSubView should not need drawing after Draw()");
  75. Assert.False (subSubView.Margin.NeedsDraw, "SubSubView's Margin should not need drawing after Draw()");
  76. // SuperView's SubViewNeedsDraw should be false because the static Draw() method
  77. // calls ClearNeedsDraw() on all the subviews at the end, AFTER Margin.DrawMargins()
  78. //
  79. // BEFORE THE FIX: This would be TRUE because Margin.DrawMargins() would
  80. // set SubViewNeedsDraw somewhere in the hierarchy and it
  81. // wouldn't be cleared
  82. // AFTER THE FIX: This is FALSE because the static Draw() calls ClearNeedsDraw()
  83. // at the very end, cleaning up any SubViewNeedsDraw flags set
  84. // by Margin.DrawMargins()
  85. Assert.False (superview.SubViewNeedsDraw,
  86. "SuperView's SubViewNeedsDraw should be false after all subviews are drawn and cleared");
  87. Assert.False (subview1.SubViewNeedsDraw,
  88. "SubView1's SubViewNeedsDraw should be false after its subviews are drawn and cleared");
  89. }
  90. [Fact]
  91. public void StaticDraw_WithForceTrue_SetsNeedsDrawOnAllViews ()
  92. {
  93. // Verify that when force=true, all views get SetNeedsDraw() called before drawing
  94. IDriver driver = CreateFakeDriver ();
  95. driver.Clip = new (driver.Screen);
  96. View view1 = new () { X = 0, Y = 0, Width = 10, Height = 10, Driver = driver, Id = "View1" };
  97. View view2 = new () { X = 10, Y = 0, Width = 10, Height = 10, Driver = driver, Id = "View2" };
  98. view1.BeginInit ();
  99. view1.EndInit ();
  100. view2.BeginInit ();
  101. view2.EndInit ();
  102. // Manually clear their NeedsDraw flags
  103. view1.Draw ();
  104. view2.Draw ();
  105. Assert.False (view1.NeedsDraw);
  106. Assert.False (view2.NeedsDraw);
  107. // Now call static Draw with force=true
  108. View.Draw ([view1, view2], force: true);
  109. // After drawing with force=true, they should be cleared again
  110. Assert.False (view1.NeedsDraw);
  111. Assert.False (view2.NeedsDraw);
  112. }
  113. [Fact]
  114. public void StaticDraw_HandlesEmptyCollection ()
  115. {
  116. // Verify that calling Draw with an empty collection doesn't crash
  117. View.Draw ([], force: false);
  118. View.Draw ([], force: true);
  119. }
  120. [Fact]
  121. public void StaticDraw_ClearsNestedSubViewNeedsDraw ()
  122. {
  123. // This test verifies that the static Draw method properly clears SubViewNeedsDraw
  124. // flags throughout a nested view hierarchy after Margin.DrawMargins
  125. IDriver driver = CreateFakeDriver ();
  126. driver.Clip = new (driver.Screen);
  127. View topView = new ()
  128. {
  129. X = 0,
  130. Y = 0,
  131. Width = 60,
  132. Height = 60,
  133. Driver = driver,
  134. Id = "TopView"
  135. };
  136. View middleView1 = new () { X = 0, Y = 0, Width = 30, Height = 30, Id = "MiddleView1" };
  137. View middleView2 = new () { X = 30, Y = 0, Width = 30, Height = 30, Id = "MiddleView2" };
  138. View bottomView = new ()
  139. {
  140. X = 5,
  141. Y = 5,
  142. Width = 15,
  143. Height = 15,
  144. Id = "BottomView"
  145. };
  146. // Give the bottom view a margin to trigger the Margin.DrawMargins behavior
  147. bottomView.Margin!.Thickness = new (1);
  148. bottomView.Margin.ShadowStyle = ShadowStyle.Transparent;
  149. middleView1.Add (bottomView);
  150. topView.Add (middleView1, middleView2);
  151. topView.BeginInit ();
  152. topView.EndInit ();
  153. topView.LayoutSubViews ();
  154. Assert.True (topView.SubViewNeedsDraw);
  155. Assert.True (middleView1.SubViewNeedsDraw);
  156. Assert.True (bottomView.NeedsDraw);
  157. // Draw the middle views using static Draw
  158. View.Draw (topView.InternalSubViews, force: false);
  159. // All SubViewNeedsDraw flags should be cleared after the static Draw
  160. Assert.False (topView.SubViewNeedsDraw,
  161. "TopView's SubViewNeedsDraw should be false after static Draw()");
  162. Assert.False (middleView1.SubViewNeedsDraw,
  163. "MiddleView1's SubViewNeedsDraw should be false after its subviews are drawn");
  164. Assert.False (middleView2.SubViewNeedsDraw,
  165. "MiddleView2's SubViewNeedsDraw should be false");
  166. Assert.False (bottomView.NeedsDraw,
  167. "BottomView should not need drawing after Draw()");
  168. Assert.False (bottomView.Margin.NeedsDraw,
  169. "BottomView's Margin should not need drawing after Draw()");
  170. }
  171. }