GetViewsUnderLocationTests.cs 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. namespace ViewBaseTests.Mouse;
  2. [Trait ("Category", "Input")]
  3. public class GetViewsUnderLocationTests
  4. {
  5. [Theory]
  6. [InlineData (0, 0)]
  7. [InlineData (2, 1)]
  8. [InlineData (20, 20)]
  9. public void Returns_Null_If_No_SubViews_Coords_Outside (int testX, int testY)
  10. {
  11. // Arrange
  12. var view = new View
  13. {
  14. Frame = new (0, 0, 10, 10)
  15. };
  16. var location = new Point (testX, testY);
  17. // Act
  18. List<View?> viewsUnderMouse = view.GetViewsUnderLocation (location, ViewportSettingsFlags.None);
  19. // Assert
  20. Assert.Empty (viewsUnderMouse);
  21. }
  22. [Theory]
  23. [InlineData (0, 0)]
  24. [InlineData (2, 1)]
  25. [InlineData (20, 20)]
  26. public void Returns_Null_If_Start_Not_Visible (int testX, int testY)
  27. {
  28. // Arrange
  29. var view = new View
  30. {
  31. Frame = new (0, 0, 10, 10),
  32. Visible = false
  33. };
  34. var location = new Point (testX, testY);
  35. // Act
  36. List<View?> viewsUnderMouse = view.GetViewsUnderLocation (location, ViewportSettingsFlags.None);
  37. // Assert
  38. Assert.Empty (viewsUnderMouse);
  39. }
  40. [Theory]
  41. [InlineData (0, 0, 0, 0, 0, -1, -1, null)]
  42. [InlineData (0, 0, 0, 0, 0, 0, 0, typeof (View))]
  43. [InlineData (0, 0, 0, 0, 0, 1, 1, typeof (View))]
  44. [InlineData (0, 0, 0, 0, 0, 4, 4, typeof (View))]
  45. [InlineData (0, 0, 0, 0, 0, 9, 9, typeof (View))]
  46. [InlineData (0, 0, 0, 0, 0, 10, 10, null)]
  47. [InlineData (1, 1, 0, 0, 0, -1, -1, null)]
  48. [InlineData (1, 1, 0, 0, 0, 0, 0, null)]
  49. [InlineData (1, 1, 0, 0, 0, 1, 1, typeof (View))]
  50. [InlineData (1, 1, 0, 0, 0, 4, 4, typeof (View))]
  51. [InlineData (1, 1, 0, 0, 0, 9, 9, typeof (View))]
  52. [InlineData (1, 1, 0, 0, 0, 10, 10, typeof (View))]
  53. [InlineData (0, 0, 1, 0, 0, -1, -1, null)]
  54. [InlineData (0, 0, 1, 0, 0, 0, 0, typeof (Margin))]
  55. [InlineData (0, 0, 1, 0, 0, 1, 1, typeof (View))]
  56. [InlineData (0, 0, 1, 0, 0, 4, 4, typeof (View))]
  57. [InlineData (0, 0, 1, 0, 0, 9, 9, typeof (Margin))]
  58. [InlineData (0, 0, 1, 0, 0, 10, 10, null)]
  59. [InlineData (0, 0, 1, 1, 0, -1, -1, null)]
  60. [InlineData (0, 0, 1, 1, 0, 0, 0, typeof (Margin))]
  61. [InlineData (0, 0, 1, 1, 0, 1, 1, typeof (Border))]
  62. [InlineData (0, 0, 1, 1, 0, 4, 4, typeof (View))]
  63. [InlineData (0, 0, 1, 1, 0, 9, 9, typeof (Margin))]
  64. [InlineData (0, 0, 1, 1, 0, 10, 10, null)]
  65. [InlineData (0, 0, 1, 1, 1, -1, -1, null)]
  66. [InlineData (0, 0, 1, 1, 1, 0, 0, typeof (Margin))]
  67. [InlineData (0, 0, 1, 1, 1, 1, 1, typeof (Border))]
  68. [InlineData (0, 0, 1, 1, 1, 2, 2, typeof (Padding))]
  69. [InlineData (0, 0, 1, 1, 1, 4, 4, typeof (View))]
  70. [InlineData (0, 0, 1, 1, 1, 9, 9, typeof (Margin))]
  71. [InlineData (0, 0, 1, 1, 1, 10, 10, null)]
  72. [InlineData (1, 1, 1, 0, 0, -1, -1, null)]
  73. [InlineData (1, 1, 1, 0, 0, 0, 0, null)]
  74. [InlineData (1, 1, 1, 0, 0, 1, 1, typeof (Margin))]
  75. [InlineData (1, 1, 1, 0, 0, 4, 4, typeof (View))]
  76. [InlineData (1, 1, 1, 0, 0, 9, 9, typeof (View))]
  77. [InlineData (1, 1, 1, 0, 0, 10, 10, typeof (Margin))]
  78. [InlineData (1, 1, 1, 1, 0, -1, -1, null)]
  79. [InlineData (1, 1, 1, 1, 0, 0, 0, null)]
  80. [InlineData (1, 1, 1, 1, 0, 1, 1, typeof (Margin))]
  81. [InlineData (1, 1, 1, 1, 0, 4, 4, typeof (View))]
  82. [InlineData (1, 1, 1, 1, 0, 9, 9, typeof (Border))]
  83. [InlineData (1, 1, 1, 1, 0, 10, 10, typeof (Margin))]
  84. [InlineData (1, 1, 1, 1, 1, -1, -1, null)]
  85. [InlineData (1, 1, 1, 1, 1, 0, 0, null)]
  86. [InlineData (1, 1, 1, 1, 1, 1, 1, typeof (Margin))]
  87. [InlineData (1, 1, 1, 1, 1, 2, 2, typeof (Border))]
  88. [InlineData (1, 1, 1, 1, 1, 3, 3, typeof (Padding))]
  89. [InlineData (1, 1, 1, 1, 1, 4, 4, typeof (View))]
  90. [InlineData (1, 1, 1, 1, 1, 8, 8, typeof (Padding))]
  91. [InlineData (1, 1, 1, 1, 1, 9, 9, typeof (Border))]
  92. [InlineData (1, 1, 1, 1, 1, 10, 10, typeof (Margin))]
  93. public void Contains (
  94. int frameX,
  95. int frameY,
  96. int marginThickness,
  97. int borderThickness,
  98. int paddingThickness,
  99. int testX,
  100. int testY,
  101. Type? expectedAdornmentType
  102. )
  103. {
  104. var view = new View
  105. {
  106. X = frameX, Y = frameY,
  107. Width = 10, Height = 10
  108. };
  109. view.Margin!.Thickness = new (marginThickness);
  110. view.Border!.Thickness = new (borderThickness);
  111. view.Padding!.Thickness = new (paddingThickness);
  112. Type? containedType = null;
  113. if (view.Contains (new (testX, testY)))
  114. {
  115. containedType = view.GetType ();
  116. }
  117. if (view.Margin!.Contains (new (testX, testY)))
  118. {
  119. containedType = view.Margin!.GetType ();
  120. }
  121. if (view.Border!.Contains (new (testX, testY)))
  122. {
  123. containedType = view.Border!.GetType ();
  124. }
  125. if (view.Padding.Contains (new (testX, testY)))
  126. {
  127. containedType = view.Padding.GetType ();
  128. }
  129. Assert.Equal (expectedAdornmentType, containedType);
  130. }
  131. [Fact]
  132. public void GetViewsUnderLocation_Returns_Adornment_Subview_When_Parent_Has_Subview_At_Same_Location ()
  133. {
  134. // Arrange - Reproduces the bug where:
  135. // - Parent has ExpanderButton in its Border
  136. // - A subview with X=-1 (extends outside parent's content) has a Border that overlaps ExpanderButton
  137. // - Bug: GetViewsUnderLocation returns subview.Border instead of ExpanderButton
  138. IApplication app = Application.Create ();
  139. Runnable<bool> runnable = new ()
  140. {
  141. Width = 50,
  142. Height = 50
  143. };
  144. app.Begin (runnable);
  145. // Create parent view
  146. var parent = new View
  147. {
  148. X = 0,
  149. Y = 0,
  150. Width = 30,
  151. Height = 10
  152. };
  153. parent.Border!.Thickness = new (1);
  154. parent.Border.ViewportSettings = ViewportSettingsFlags.None;
  155. // Add ExpanderButton to parent's Border at (0, 0)
  156. // Since parent.Border has thickness=1, the Border's viewport starts at (0,0) screen coords
  157. // And the ExpanderButton at (0,0) relative to Border viewport is at screen (0,0)
  158. var expanderButton = new Button
  159. {
  160. X = 0,
  161. Y = 0,
  162. Width = 1,
  163. Height = 1,
  164. Text = ">",
  165. ShadowStyle = ShadowStyle.None
  166. };
  167. parent.Border.Add (expanderButton);
  168. // Add a subview at X=-1, Y=-1 (extends outside parent's Viewport in both dimensions)
  169. // The subview's Border will overlap with the ExpanderButton location
  170. var childView = new View
  171. {
  172. X = -1, // This causes child's left edge to be at screen X=0 (parent content starts at X=1)
  173. Y = -1, // This causes child's top edge to be at screen Y=0 (parent content starts at Y=1)
  174. Width = 20,
  175. Height = 5
  176. };
  177. childView.Border!.Thickness = new (1);
  178. childView.Border!.ViewportSettings = ViewportSettingsFlags.None;
  179. parent.Add (childView);
  180. runnable.Add (parent);
  181. runnable.Layout ();
  182. // Get screen location of ExpanderButton
  183. Rectangle buttonFrame = expanderButton.FrameToScreen ();
  184. Point testLocation = buttonFrame.Location;
  185. // Verify that childView.Border also contains this location (this is the bug scenario)
  186. Rectangle childBorderFrame = childView.Border.FrameToScreen ();
  187. Assert.True (
  188. childBorderFrame.Contains (testLocation),
  189. $"Test setup failed: childView.Border ({childBorderFrame}) should contain testLocation ({testLocation})");
  190. // Act
  191. List<View?> viewsUnderLocation = runnable.GetViewsUnderLocation (testLocation, ViewportSettingsFlags.None);
  192. // Assert
  193. View? deepestView = viewsUnderLocation.LastOrDefault ();
  194. Assert.NotNull (deepestView);
  195. // The ExpanderButton is a subview of parent.Border, which is processed before childView
  196. // But childView.Border is processed AFTER ExpanderButton, causing the bug
  197. // The correct deepest view should be ExpanderButton, not childView.Border
  198. Assert.Equal (expanderButton, deepestView);
  199. app.Dispose ();
  200. }
  201. }