KeyboardTests.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. using Xunit.Abstractions;
  2. namespace Terminal.Gui.ApplicationTests;
  3. /// <summary>
  4. /// Application tests for keyboard support.
  5. /// </summary>
  6. public class KeyboardTests
  7. {
  8. private readonly ITestOutputHelper _output;
  9. public KeyboardTests (ITestOutputHelper output)
  10. {
  11. _output = output;
  12. #if DEBUG_IDISPOSABLE
  13. Responder.Instances.Clear ();
  14. RunState.Instances.Clear ();
  15. #endif
  16. }
  17. [Fact]
  18. [AutoInitShutdown]
  19. public void QuitKey_Getter_Setter ()
  20. {
  21. Toplevel top = new ();
  22. var isQuiting = false;
  23. top.Closing += (s, e) =>
  24. {
  25. isQuiting = true;
  26. e.Cancel = true;
  27. };
  28. Application.Begin (top);
  29. top.Running = true;
  30. Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, Application.QuitKey.KeyCode);
  31. Application.Driver.SendKeys ('Q', ConsoleKey.Q, false, false, true);
  32. Assert.True (isQuiting);
  33. isQuiting = false;
  34. Application.OnKeyDown (Key.Q.WithCtrl);
  35. Assert.True (isQuiting);
  36. isQuiting = false;
  37. Application.QuitKey = Key.C.WithCtrl;
  38. Application.Driver.SendKeys ('Q', ConsoleKey.Q, false, false, true);
  39. Assert.False (isQuiting);
  40. Application.OnKeyDown (Key.Q.WithCtrl);
  41. Assert.False (isQuiting);
  42. Application.OnKeyDown (Application.QuitKey);
  43. Assert.True (isQuiting);
  44. // Reset the QuitKey to avoid throws errors on another tests
  45. Application.QuitKey = Key.Q.WithCtrl;
  46. top.Dispose ();
  47. }
  48. [Fact]
  49. public void AlternateForwardKey_AlternateBackwardKey_Tests ()
  50. {
  51. Application.Init (new FakeDriver ());
  52. Toplevel top = new ();
  53. var w1 = new Window ();
  54. var v1 = new TextField ();
  55. var v2 = new TextView ();
  56. w1.Add (v1, v2);
  57. var w2 = new Window ();
  58. var v3 = new CheckBox ();
  59. var v4 = new Button ();
  60. w2.Add (v3, v4);
  61. top.Add (w1, w2);
  62. Application.Iteration += (s, a) =>
  63. {
  64. Assert.True (v1.HasFocus);
  65. // Using default keys.
  66. top.NewKeyDownEvent (Key.Tab.WithCtrl);
  67. Assert.True (v2.HasFocus);
  68. top.NewKeyDownEvent (Key.Tab.WithCtrl);
  69. Assert.True (v3.HasFocus);
  70. top.NewKeyDownEvent (Key.Tab.WithCtrl);
  71. Assert.True (v4.HasFocus);
  72. top.NewKeyDownEvent (Key.Tab.WithCtrl);
  73. Assert.True (v1.HasFocus);
  74. top.NewKeyDownEvent (Key.Tab.WithShift.WithCtrl);
  75. Assert.True (v4.HasFocus);
  76. top.NewKeyDownEvent (Key.Tab.WithShift.WithCtrl);
  77. Assert.True (v3.HasFocus);
  78. top.NewKeyDownEvent (Key.Tab.WithShift.WithCtrl);
  79. Assert.True (v2.HasFocus);
  80. top.NewKeyDownEvent (Key.Tab.WithShift.WithCtrl);
  81. Assert.True (v1.HasFocus);
  82. top.NewKeyDownEvent (Key.PageDown.WithCtrl);
  83. Assert.True (v2.HasFocus);
  84. top.NewKeyDownEvent (Key.PageDown.WithCtrl);
  85. Assert.True (v3.HasFocus);
  86. top.NewKeyDownEvent (Key.PageDown.WithCtrl);
  87. Assert.True (v4.HasFocus);
  88. top.NewKeyDownEvent (Key.PageDown.WithCtrl);
  89. Assert.True (v1.HasFocus);
  90. top.NewKeyDownEvent (Key.PageUp.WithCtrl);
  91. Assert.True (v4.HasFocus);
  92. top.NewKeyDownEvent (Key.PageUp.WithCtrl);
  93. Assert.True (v3.HasFocus);
  94. top.NewKeyDownEvent (Key.PageUp.WithCtrl);
  95. Assert.True (v2.HasFocus);
  96. top.NewKeyDownEvent (Key.PageUp.WithCtrl);
  97. Assert.True (v1.HasFocus);
  98. // Using another's alternate keys.
  99. Application.AlternateForwardKey = Key.F7;
  100. Application.AlternateBackwardKey = Key.F6;
  101. top.NewKeyDownEvent (Key.F7);
  102. Assert.True (v2.HasFocus);
  103. top.NewKeyDownEvent (Key.F7);
  104. Assert.True (v3.HasFocus);
  105. top.NewKeyDownEvent (Key.F7);
  106. Assert.True (v4.HasFocus);
  107. top.NewKeyDownEvent (Key.F7);
  108. Assert.True (v1.HasFocus);
  109. top.NewKeyDownEvent (Key.F6);
  110. Assert.True (v4.HasFocus);
  111. top.NewKeyDownEvent (Key.F6);
  112. Assert.True (v3.HasFocus);
  113. top.NewKeyDownEvent (Key.F6);
  114. Assert.True (v2.HasFocus);
  115. top.NewKeyDownEvent (Key.F6);
  116. Assert.True (v1.HasFocus);
  117. Application.RequestStop ();
  118. };
  119. Application.Run (top);
  120. // Replacing the defaults keys to avoid errors on others unit tests that are using it.
  121. Application.AlternateForwardKey = Key.PageDown.WithCtrl;
  122. Application.AlternateBackwardKey = Key.PageUp.WithCtrl;
  123. Application.QuitKey = Key.Q.WithCtrl;
  124. Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, Application.AlternateForwardKey.KeyCode);
  125. Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, Application.AlternateBackwardKey.KeyCode);
  126. Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, Application.QuitKey.KeyCode);
  127. top.Dispose ();
  128. // Shutdown must be called to safely clean up Application if Init has been called
  129. Application.Shutdown ();
  130. }
  131. [Fact]
  132. [AutoInitShutdown]
  133. public void EnsuresTopOnFront_CanFocus_False_By_Keyboard ()
  134. {
  135. Toplevel top = new ();
  136. var win = new Window
  137. {
  138. Title = "win",
  139. X = 0,
  140. Y = 0,
  141. Width = 20,
  142. Height = 10
  143. };
  144. var tf = new TextField { Width = 10 };
  145. win.Add (tf);
  146. var win2 = new Window
  147. {
  148. Title = "win2",
  149. X = 22,
  150. Y = 0,
  151. Width = 20,
  152. Height = 10
  153. };
  154. var tf2 = new TextField { Width = 10 };
  155. win2.Add (tf2);
  156. top.Add (win, win2);
  157. Application.Begin (top);
  158. Assert.True (win.CanFocus);
  159. Assert.True (win.HasFocus);
  160. Assert.True (win2.CanFocus);
  161. Assert.False (win2.HasFocus);
  162. Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
  163. win.CanFocus = false;
  164. Assert.False (win.CanFocus);
  165. Assert.False (win.HasFocus);
  166. Assert.True (win2.CanFocus);
  167. Assert.True (win2.HasFocus);
  168. Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
  169. top.NewKeyDownEvent (Key.Tab.WithCtrl);
  170. Assert.True (win2.CanFocus);
  171. Assert.False (win.HasFocus);
  172. Assert.True (win2.CanFocus);
  173. Assert.True (win2.HasFocus);
  174. Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
  175. top.NewKeyDownEvent (Key.Tab.WithCtrl);
  176. Assert.False (win.CanFocus);
  177. Assert.False (win.HasFocus);
  178. Assert.True (win2.CanFocus);
  179. Assert.True (win2.HasFocus);
  180. Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
  181. top.Dispose ();
  182. }
  183. [Fact]
  184. [AutoInitShutdown]
  185. public void EnsuresTopOnFront_CanFocus_True_By_Keyboard_ ()
  186. {
  187. Toplevel top = new ();
  188. var win = new Window
  189. {
  190. Title = "win",
  191. X = 0,
  192. Y = 0,
  193. Width = 20,
  194. Height = 10
  195. };
  196. var tf = new TextField { Width = 10 };
  197. win.Add (tf);
  198. var win2 = new Window
  199. {
  200. Title = "win2",
  201. X = 22,
  202. Y = 0,
  203. Width = 20,
  204. Height = 10
  205. };
  206. var tf2 = new TextField { Width = 10 };
  207. win2.Add (tf2);
  208. top.Add (win, win2);
  209. Application.Begin (top);
  210. Assert.True (win.CanFocus);
  211. Assert.True (win.HasFocus);
  212. Assert.True (win2.CanFocus);
  213. Assert.False (win2.HasFocus);
  214. Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
  215. top.NewKeyDownEvent (Key.Tab.WithCtrl);
  216. Assert.True (win.CanFocus);
  217. Assert.False (win.HasFocus);
  218. Assert.True (win2.CanFocus);
  219. Assert.True (win2.HasFocus);
  220. Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
  221. top.NewKeyDownEvent (Key.Tab.WithCtrl);
  222. Assert.True (win.CanFocus);
  223. Assert.True (win.HasFocus);
  224. Assert.True (win2.CanFocus);
  225. Assert.False (win2.HasFocus);
  226. Assert.Equal ("win", ((Window)top.Subviews [^1]).Title);
  227. top.Dispose ();
  228. }
  229. [Fact]
  230. public void KeyUp_Event ()
  231. {
  232. Application.Init (new FakeDriver ());
  233. // Setup some fake keypresses (This)
  234. var input = "Tests";
  235. // Put a control-q in at the end
  236. FakeConsole.MockKeyPresses.Push (new ConsoleKeyInfo ('Q', ConsoleKey.Q, false, false, true));
  237. foreach (char c in input.Reverse ())
  238. {
  239. if (char.IsLetter (c))
  240. {
  241. FakeConsole.MockKeyPresses.Push (
  242. new ConsoleKeyInfo (
  243. c,
  244. (ConsoleKey)char.ToUpper (c),
  245. char.IsUpper (c),
  246. false,
  247. false
  248. )
  249. );
  250. }
  251. else
  252. {
  253. FakeConsole.MockKeyPresses.Push (
  254. new ConsoleKeyInfo (
  255. c,
  256. (ConsoleKey)c,
  257. false,
  258. false,
  259. false
  260. )
  261. );
  262. }
  263. }
  264. int stackSize = FakeConsole.MockKeyPresses.Count;
  265. var iterations = 0;
  266. Application.Iteration += (s, a) =>
  267. {
  268. iterations++;
  269. // Stop if we run out of control...
  270. if (iterations > 10)
  271. {
  272. Application.RequestStop ();
  273. }
  274. };
  275. var keyUps = 0;
  276. var output = string.Empty;
  277. var top = new Toplevel ();
  278. top.KeyUp += (sender, args) =>
  279. {
  280. if (args.KeyCode != (KeyCode.CtrlMask | KeyCode.Q))
  281. {
  282. output += args.AsRune;
  283. }
  284. keyUps++;
  285. };
  286. Application.Run (top);
  287. // Input string should match output
  288. Assert.Equal (input, output);
  289. // # of key up events should match stack size
  290. //Assert.Equal (stackSize, keyUps);
  291. // We can't use numbers variables on the left side of an Assert.Equal/NotEqual,
  292. // it must be literal (Linux only).
  293. Assert.Equal (6, keyUps);
  294. // # of key up events should match # of iterations
  295. Assert.Equal (stackSize, iterations);
  296. top.Dispose ();
  297. Application.Shutdown ();
  298. Assert.Null (Application.Current);
  299. Assert.Null (Application.Top);
  300. Assert.Null (Application.MainLoop);
  301. Assert.Null (Application.Driver);
  302. }
  303. [Fact]
  304. [AutoInitShutdown]
  305. public void KeyBinding_OnKeyDown ()
  306. {
  307. var view = new ScopedKeyBindingView ();
  308. var invoked = false;
  309. view.InvokingKeyBindings += (s, e) => invoked = true;
  310. var top = new Toplevel ();
  311. top.Add (view);
  312. Application.Begin (top);
  313. Application.OnKeyDown (Key.A);
  314. Assert.True (invoked);
  315. Assert.True (view.ApplicationCommand);
  316. invoked = false;
  317. view.ApplicationCommand = false;
  318. view.KeyBindings.Remove (KeyCode.A);
  319. Application.OnKeyDown (Key.A); // old
  320. Assert.False (invoked);
  321. Assert.False (view.ApplicationCommand);
  322. view.KeyBindings.Add (Key.A.WithCtrl, KeyBindingScope.Application, Command.Save);
  323. Application.OnKeyDown (Key.A); // old
  324. Assert.False (invoked);
  325. Assert.False (view.ApplicationCommand);
  326. Application.OnKeyDown (Key.A.WithCtrl); // new
  327. Assert.True (invoked);
  328. Assert.True (view.ApplicationCommand);
  329. invoked = false;
  330. Application.OnKeyDown (Key.H);
  331. Assert.True (invoked);
  332. invoked = false;
  333. Assert.False (view.HasFocus);
  334. Application.OnKeyDown (Key.F);
  335. Assert.False (invoked);
  336. Assert.True (view.ApplicationCommand);
  337. Assert.True (view.HotKeyCommand);
  338. Assert.False (view.FocusedCommand);
  339. top.Dispose ();
  340. }
  341. [Fact]
  342. [AutoInitShutdown]
  343. public void KeyBinding_OnKeyDown_Negative ()
  344. {
  345. var view = new ScopedKeyBindingView ();
  346. var invoked = false;
  347. view.InvokingKeyBindings += (s, e) => invoked = true;
  348. var top = new Toplevel ();
  349. top.Add (view);
  350. Application.Begin (top);
  351. Application.OnKeyDown (Key.A.WithCtrl);
  352. Assert.False (invoked);
  353. Assert.False (view.ApplicationCommand);
  354. Assert.False (view.HotKeyCommand);
  355. Assert.False (view.FocusedCommand);
  356. invoked = false;
  357. Assert.False (view.HasFocus);
  358. Application.OnKeyDown (Key.Z);
  359. Assert.False (invoked);
  360. Assert.False (view.ApplicationCommand);
  361. Assert.False (view.HotKeyCommand);
  362. Assert.False (view.FocusedCommand);
  363. top.Dispose ();
  364. }
  365. [Fact]
  366. [AutoInitShutdown]
  367. public void KeyBinding_AddKeyBinding_Adds ()
  368. {
  369. View view1 = new ();
  370. Application.AddKeyBinding (Key.A, view1);
  371. View view2 = new ();
  372. Application.AddKeyBinding (Key.A, view2);
  373. Assert.True (Application.TryGetKeyBindings (Key.A, out List<View> views));
  374. Assert.Contains (view1, views);
  375. Assert.Contains (view2, views);
  376. Assert.False (Application.TryGetKeyBindings (Key.B, out List<View> _));
  377. }
  378. [Fact]
  379. [AutoInitShutdown]
  380. public void KeyBinding_ViewKeyBindings_Add_Adds ()
  381. {
  382. View view1 = new ();
  383. view1.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Save);
  384. view1.KeyBindings.Add (Key.B, KeyBindingScope.HotKey, Command.Left);
  385. Assert.Single (Application.GetViewsWithKeyBindings ());
  386. View view2 = new ();
  387. view2.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Save);
  388. view2.KeyBindings.Add (Key.B, KeyBindingScope.HotKey, Command.Left);
  389. Assert.True (Application.TryGetKeyBindings (Key.A, out List<View> views));
  390. Assert.Contains (view1, views);
  391. Assert.Contains (view2, views);
  392. Assert.False (Application.TryGetKeyBindings (Key.B, out List<View> _));
  393. }
  394. [Fact]
  395. [AutoInitShutdown]
  396. public void KeyBinding_RemoveKeyBinding_Removes ()
  397. {
  398. View view1 = new ();
  399. Application.AddKeyBinding (Key.A, view1);
  400. Assert.True (Application.TryGetKeyBindings (Key.A, out List<View> views));
  401. Assert.Contains (view1, views);
  402. Application.RemoveKeyBinding (Key.A, view1);
  403. Assert.False (Application.TryGetKeyBindings (Key.A, out List<View> _));
  404. }
  405. [Fact]
  406. [AutoInitShutdown]
  407. public void KeyBinding_ViewKeyBindings_RemoveKeyBinding_Removes ()
  408. {
  409. View view1 = new ();
  410. view1.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Save);
  411. Assert.True (Application.TryGetKeyBindings (Key.A, out List<View> views));
  412. Assert.Contains (view1, views);
  413. view1.KeyBindings.Remove (Key.A);
  414. Assert.False (Application.TryGetKeyBindings (Key.A, out List<View> _));
  415. }
  416. // Test View for testing Application key Bindings
  417. public class ScopedKeyBindingView : View
  418. {
  419. public ScopedKeyBindingView ()
  420. {
  421. AddCommand (Command.Save, () => ApplicationCommand = true);
  422. AddCommand (Command.HotKey, () => HotKeyCommand = true);
  423. AddCommand (Command.Left, () => FocusedCommand = true);
  424. KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Save);
  425. HotKey = KeyCode.H;
  426. KeyBindings.Add (Key.F, KeyBindingScope.Focused, Command.Left);
  427. }
  428. public bool ApplicationCommand { get; set; }
  429. public bool FocusedCommand { get; set; }
  430. public bool HotKeyCommand { get; set; }
  431. }
  432. }