StaticDrawTests.cs 7.8 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 static Draw(). All subviews were drawn in the call to View.Draw");
  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(). All subviews were drawn in the call to View.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. }