ApplicationMouseTests.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. #nullable enable
  2. using UnitTests;
  3. using Xunit.Abstractions;
  4. // Alias Console to MockConsole so we don't accidentally use Console
  5. namespace UnitTests.ApplicationTests;
  6. [Trait ("Category", "Input")]
  7. public class ApplicationMouseTests
  8. {
  9. private readonly ITestOutputHelper _output;
  10. public ApplicationMouseTests (ITestOutputHelper output)
  11. {
  12. _output = output;
  13. #if DEBUG_IDISPOSABLE
  14. View.Instances.Clear ();
  15. #endif
  16. }
  17. #region mouse coordinate tests
  18. // test Application.MouseEvent - ensure coordinates are screen relative
  19. [Theory]
  20. // inside tests
  21. [InlineData (0, 0, 0, 0, true)]
  22. [InlineData (1, 0, 1, 0, true)]
  23. [InlineData (0, 1, 0, 1, true)]
  24. [InlineData (9, 0, 9, 0, true)]
  25. [InlineData (0, 9, 0, 9, true)]
  26. // outside tests
  27. [InlineData (-1, -1, -1, -1, true)]
  28. [InlineData (0, -1, 0, -1, true)]
  29. [InlineData (-1, 0, -1, 0, true)]
  30. public void MouseEventCoordinatesAreScreenRelative (
  31. int clickX,
  32. int clickY,
  33. int expectedX,
  34. int expectedY,
  35. bool expectedClicked
  36. )
  37. {
  38. var mouseEvent = new MouseEventArgs { ScreenPosition = new (clickX, clickY), Flags = MouseFlags.Button1Pressed };
  39. var clicked = false;
  40. void OnApplicationOnMouseEvent (object? s, MouseEventArgs e)
  41. {
  42. Assert.Equal (expectedX, e.ScreenPosition.X);
  43. Assert.Equal (expectedY, e.ScreenPosition.Y);
  44. clicked = true;
  45. }
  46. Application.MouseEvent += OnApplicationOnMouseEvent;
  47. Application.RaiseMouseEvent (mouseEvent);
  48. Assert.Equal (expectedClicked, clicked);
  49. Application.MouseEvent -= OnApplicationOnMouseEvent;
  50. }
  51. /// <summary>
  52. /// Tests that the mouse coordinates passed to the focused view are correct when the mouse is clicked. No adornments;
  53. /// Frame == Viewport
  54. /// </summary>
  55. [Theory]
  56. [AutoInitShutdown]
  57. // click inside view tests
  58. [InlineData (0, 0, 0, 0, 0, true)]
  59. [InlineData (0, 1, 0, 1, 0, true)]
  60. [InlineData (0, 0, 1, 0, 1, true)]
  61. [InlineData (0, 9, 0, 9, 0, true)]
  62. [InlineData (0, 0, 9, 0, 9, true)]
  63. // view is offset from origin ; click is inside view
  64. [InlineData (1, 1, 1, 0, 0, true)]
  65. [InlineData (1, 2, 1, 1, 0, true)]
  66. [InlineData (1, 1, 2, 0, 1, true)]
  67. [InlineData (1, 9, 1, 8, 0, true)]
  68. [InlineData (1, 1, 9, 0, 8, true)]
  69. // click outside view tests
  70. [InlineData (0, -1, -1, 0, 0, false)]
  71. [InlineData (0, 0, -1, 0, 0, false)]
  72. [InlineData (0, -1, 0, 0, 0, false)]
  73. [InlineData (0, 0, 10, 0, 0, false)]
  74. [InlineData (0, 10, 0, 0, 0, false)]
  75. [InlineData (0, 10, 10, 0, 0, false)]
  76. // view is offset from origin ; click is outside view
  77. [InlineData (1, 0, 0, 0, 0, false)]
  78. [InlineData (1, 1, 0, 0, 0, false)]
  79. [InlineData (1, 0, 1, 0, 0, false)]
  80. [InlineData (1, 9, 0, 0, 0, false)]
  81. [InlineData (1, 0, 9, 0, 0, false)]
  82. public void MouseCoordinatesTest_NoAdornments (
  83. int offset,
  84. int clickX,
  85. int clickY,
  86. int expectedX,
  87. int expectedY,
  88. bool expectedClicked
  89. )
  90. {
  91. Size size = new (10, 10);
  92. Point pos = new (offset, offset);
  93. var clicked = false;
  94. var view = new View
  95. {
  96. X = pos.X,
  97. Y = pos.Y,
  98. Width = size.Width,
  99. Height = size.Height
  100. };
  101. var mouseEvent = new MouseEventArgs { ScreenPosition = new (clickX, clickY), Flags = MouseFlags.Button1Clicked };
  102. view.MouseEvent += (s, e) =>
  103. {
  104. Assert.Equal (expectedX, e.Position.X);
  105. Assert.Equal (expectedY, e.Position.Y);
  106. clicked = true;
  107. };
  108. var top = new Runnable ();
  109. top.Add (view);
  110. Application.Begin (top);
  111. Application.RaiseMouseEvent (mouseEvent);
  112. Assert.Equal (expectedClicked, clicked);
  113. top.Dispose ();
  114. }
  115. #endregion mouse coordinate tests
  116. #region mouse grab tests
  117. [Fact (Skip = "Rebuild to use ScrollBar")]
  118. [AutoInitShutdown]
  119. public void MouseGrabView_WithNullMouseEventView ()
  120. {
  121. //var tf = new TextField { Width = 10 };
  122. //var sv = new ScrollView { Width = Dim.Fill (), Height = Dim.Fill () };
  123. //sv.SetContentSize (new (100, 100));
  124. //sv.Add (tf);
  125. //var top = new Runnable ();
  126. //top.Add (sv);
  127. //int iterations = -1;
  128. //ApplicationImpl.Instance.Iteration += (s, a) =>
  129. // {
  130. // iterations++;
  131. // if (iterations == 0)
  132. // {
  133. // Assert.True (tf.HasFocus);
  134. // Assert.Null (Application.Mouse.MouseGrabView);
  135. // Application.RaiseMouseEvent (new () { ScreenPosition = new (5, 5), Flags = MouseFlags.ReportMousePosition });
  136. // Assert.Equal (sv, Application.Mouse.MouseGrabView);
  137. // MessageBox.Query (App, "Title", "Test", "Ok");
  138. // Assert.Null (Application.Mouse.MouseGrabView);
  139. // }
  140. // else if (iterations == 1)
  141. // {
  142. // // Application.Mouse.MouseGrabView is null because
  143. // // another runnable (Dialog) was opened
  144. // Assert.Null (Application.Mouse.MouseGrabView);
  145. // Application.RaiseMouseEvent (new () { ScreenPosition = new (5, 5), Flags = MouseFlags.ReportMousePosition });
  146. // Assert.Null (Application.Mouse.MouseGrabView);
  147. // Application.RaiseMouseEvent (new () { ScreenPosition = new (40, 12), Flags = MouseFlags.ReportMousePosition });
  148. // Assert.Null (Application.Mouse.MouseGrabView);
  149. // Application.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Pressed });
  150. // Assert.Null (Application.Mouse.MouseGrabView);
  151. // Application.RequestStop ();
  152. // }
  153. // else if (iterations == 2)
  154. // {
  155. // Assert.Null (Application.Mouse.MouseGrabView);
  156. // Application.RequestStop ();
  157. // }
  158. // };
  159. //Application.Run (top);
  160. //top.Dispose ();
  161. }
  162. [Fact]
  163. [AutoInitShutdown]
  164. public void MouseGrabView_GrabbedMouse_UnGrabbedMouse ()
  165. {
  166. View? grabView = null;
  167. var count = 0;
  168. var view1 = new View { Id = "view1" };
  169. var view2 = new View { Id = "view2" };
  170. var view3 = new View { Id = "view3" };
  171. Application.Mouse.GrabbedMouse += Application_GrabbedMouse;
  172. Application.Mouse.UnGrabbedMouse += Application_UnGrabbedMouse;
  173. Application.Mouse.GrabMouse (view1);
  174. Assert.Equal (0, count);
  175. Assert.Equal (grabView, view1);
  176. Assert.Equal (view1, Application.Mouse.MouseGrabView);
  177. Application.Mouse.UngrabMouse ();
  178. Assert.Equal (1, count);
  179. Assert.Equal (grabView, view1);
  180. Assert.Null (Application.Mouse.MouseGrabView);
  181. Application.Mouse.GrabbedMouse += Application_GrabbedMouse;
  182. Application.Mouse.UnGrabbedMouse += Application_UnGrabbedMouse;
  183. Application.Mouse.GrabMouse (view2);
  184. Assert.Equal (1, count);
  185. Assert.Equal (grabView, view2);
  186. Assert.Equal (view2, Application.Mouse.MouseGrabView);
  187. Application.Mouse.UngrabMouse ();
  188. Assert.Equal (2, count);
  189. Assert.Equal (grabView, view2);
  190. Assert.Equal (view3, Application.Mouse.MouseGrabView);
  191. Application.Mouse.UngrabMouse ();
  192. Assert.Null (Application.Mouse.MouseGrabView);
  193. void Application_GrabbedMouse (object? sender, ViewEventArgs e)
  194. {
  195. if (count == 0)
  196. {
  197. Assert.Equal (view1, e.View);
  198. grabView = view1;
  199. }
  200. else
  201. {
  202. Assert.Equal (view2, e.View);
  203. grabView = view2;
  204. }
  205. Application.Mouse.GrabbedMouse -= Application_GrabbedMouse;
  206. }
  207. void Application_UnGrabbedMouse (object? sender, ViewEventArgs e)
  208. {
  209. if (count == 0)
  210. {
  211. Assert.Equal (view1, e.View);
  212. Assert.Equal (grabView, e.View);
  213. }
  214. else
  215. {
  216. Assert.Equal (view2, e.View);
  217. Assert.Equal (grabView, e.View);
  218. }
  219. count++;
  220. if (count > 1)
  221. {
  222. // It's possible to grab another view after the previous was ungrabbed
  223. Application.Mouse.GrabMouse (view3);
  224. }
  225. Application.Mouse.UnGrabbedMouse -= Application_UnGrabbedMouse;
  226. }
  227. }
  228. [Fact]
  229. [AutoInitShutdown]
  230. public void View_Is_Responsible_For_Calling_UnGrabMouse_Before_Being_Disposed ()
  231. {
  232. var count = 0;
  233. var view = new View { Width = 1, Height = 1 };
  234. view.MouseEvent += (s, e) => count++;
  235. var top = new Runnable ();
  236. top.Add (view);
  237. Application.Begin (top);
  238. Assert.Null (Application.Mouse.MouseGrabView);
  239. Application.Mouse.GrabMouse (view);
  240. Assert.Equal (view, Application.Mouse.MouseGrabView);
  241. top.Remove (view);
  242. Application.Mouse.UngrabMouse ();
  243. view.Dispose ();
  244. #if DEBUG_IDISPOSABLE
  245. Assert.True (view.WasDisposed);
  246. #endif
  247. Application.RaiseMouseEvent (new () { ScreenPosition = new (0, 0), Flags = MouseFlags.Button1Pressed });
  248. Assert.Null (Application.Mouse.MouseGrabView);
  249. Assert.Equal (0, count);
  250. top.Dispose ();
  251. }
  252. [Fact]
  253. [AutoInitShutdown]
  254. public void MouseGrab_EventSentToGrabView_HasCorrectView ()
  255. {
  256. // BEFORE FIX: viewRelativeMouseEvent.View = deepestViewUnderMouse ?? MouseGrabView (potentially targetView).
  257. // AFTER FIX: viewRelativeMouseEvent.View = MouseGrabView (always the grab view).
  258. // Test fails before fix (receivedView == targetView), passes after fix (receivedView == grabView).
  259. var grabView = new View
  260. {
  261. Id = "grab",
  262. X = 0,
  263. Y = 0,
  264. Width = 5,
  265. Height = 5
  266. };
  267. var targetView = new View
  268. {
  269. Id = "target",
  270. X = 0,
  271. Y = 0,
  272. Width = 5,
  273. Height = 5
  274. };
  275. View? receivedView = null;
  276. grabView.MouseEvent += (_, e) => receivedView = e.View;
  277. var top = new Runnable { Width = 20, Height = 10 };
  278. top.Add (grabView);
  279. top.Add (targetView); // deepestViewUnderMouse = targetView
  280. Application.Begin (top);
  281. Application.Mouse.GrabMouse (grabView);
  282. Assert.Equal (grabView, Application.Mouse.MouseGrabView);
  283. Application.RaiseMouseEvent (new MouseEventArgs
  284. {
  285. ScreenPosition = new (2, 2), // Inside both views
  286. Flags = MouseFlags.Button1Clicked
  287. });
  288. // EXPECTED: Event sent to grab view has View == grabView.
  289. Assert.Equal (grabView, receivedView);
  290. Application.Mouse.UngrabMouse ();
  291. top.Dispose ();
  292. }
  293. #endregion
  294. }