HotKeyTests.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. using System.Text;
  2. using Xunit.Abstractions;
  3. namespace Terminal.Gui.ViewTests;
  4. public class HotKeyTests
  5. {
  6. [Theory]
  7. [InlineData (KeyCode.A)]
  8. [InlineData (KeyCode.A | KeyCode.ShiftMask)]
  9. [InlineData (KeyCode.D1)]
  10. [InlineData (KeyCode.D1 | KeyCode.ShiftMask)] // '!'
  11. [InlineData ((KeyCode)'х')] // Cyrillic x
  12. [InlineData ((KeyCode)'你')] // Chinese ni
  13. public void AddKeyBindingsForHotKey_Sets (KeyCode key)
  14. {
  15. var view = new View ();
  16. view.HotKey = KeyCode.Z;
  17. Assert.Equal (string.Empty, view.Title);
  18. Assert.Equal (KeyCode.Z, view.HotKey);
  19. view.AddKeyBindingsForHotKey (KeyCode.Null, key);
  20. // Verify key bindings were set
  21. // As passed
  22. Command [] commands = view.HotKeyBindings.GetCommands (key);
  23. Assert.Contains (Command.HotKey, commands);
  24. commands = view.HotKeyBindings.GetCommands (key | KeyCode.AltMask);
  25. Assert.Contains (Command.HotKey, commands);
  26. KeyCode baseKey = key & ~KeyCode.ShiftMask;
  27. // If A...Z, with and without shift
  28. if (baseKey is >= KeyCode.A and <= KeyCode.Z)
  29. {
  30. commands = view.HotKeyBindings.GetCommands (key | KeyCode.ShiftMask);
  31. Assert.Contains (Command.HotKey, commands);
  32. commands = view.HotKeyBindings.GetCommands (key & ~KeyCode.ShiftMask);
  33. Assert.Contains (Command.HotKey, commands);
  34. commands = view.HotKeyBindings.GetCommands (key | KeyCode.AltMask);
  35. Assert.Contains (Command.HotKey, commands);
  36. commands = view.HotKeyBindings.GetCommands ((key & ~KeyCode.ShiftMask) | KeyCode.AltMask);
  37. Assert.Contains (Command.HotKey, commands);
  38. }
  39. else
  40. {
  41. // Non A..Z keys should not have shift bindings
  42. if (key.HasFlag (KeyCode.ShiftMask))
  43. {
  44. commands = view.HotKeyBindings.GetCommands (key & ~KeyCode.ShiftMask);
  45. Assert.Empty (commands);
  46. }
  47. else
  48. {
  49. commands = view.HotKeyBindings.GetCommands (key | KeyCode.ShiftMask);
  50. Assert.Empty (commands);
  51. }
  52. }
  53. }
  54. [Fact]
  55. public void AddKeyBindingsForHotKey_SetsBinding_Key ()
  56. {
  57. var view = new View ();
  58. view.HotKey = KeyCode.Z;
  59. Assert.Equal (string.Empty, view.Title);
  60. Assert.Equal (KeyCode.Z, view.HotKey);
  61. view.AddKeyBindingsForHotKey (view.HotKey, Key.A);
  62. view.HotKeyBindings.TryGet (Key.A, out var binding);
  63. Assert.Equal (Key.A, binding.Key);
  64. }
  65. [Fact]
  66. public void AddKeyBindingsForHotKey_SetsBinding_Data ()
  67. {
  68. var view = new View ();
  69. view.HotKey = KeyCode.Z;
  70. Assert.Equal (KeyCode.Z, view.HotKey);
  71. view.AddKeyBindingsForHotKey (view.HotKey, Key.A, "data");
  72. view.HotKeyBindings.TryGet (Key.A, out var binding);
  73. Assert.Equal ("data", binding.Data);
  74. }
  75. [Fact]
  76. public void Defaults ()
  77. {
  78. var view = new View ();
  79. Assert.Equal (string.Empty, view.Title);
  80. Assert.Equal (KeyCode.Null, view.HotKey);
  81. // Verify key bindings were set
  82. Command [] commands = view.KeyBindings.GetCommands (KeyCode.Null);
  83. Assert.Empty (commands);
  84. commands = view.HotKeyBindings.GetCommands (KeyCode.Null);
  85. Assert.Empty (commands);
  86. Assert.Empty (view.HotKeyBindings.GetBindings ());
  87. }
  88. [Theory]
  89. [InlineData (KeyCode.Null, true)] // non-shift
  90. [InlineData (KeyCode.ShiftMask, true)]
  91. [InlineData (KeyCode.AltMask, true)]
  92. [InlineData (KeyCode.ShiftMask | KeyCode.AltMask, true)]
  93. [InlineData (KeyCode.CtrlMask, false)]
  94. [InlineData (KeyCode.ShiftMask | KeyCode.CtrlMask, false)]
  95. public void NewKeyDownEvent_Runs_Default_HotKey_Command (KeyCode mask, bool expected)
  96. {
  97. var view = new View { HotKeySpecifier = (Rune)'^', Title = "^Test" };
  98. view.CanFocus = true;
  99. Assert.False (view.HasFocus);
  100. view.NewKeyDownEvent (KeyCode.T | mask);
  101. Assert.Equal (expected, view.HasFocus);
  102. }
  103. [Fact]
  104. public void NewKeyDownEvent_Ignores_Focus_KeyBindings_SuperView ()
  105. {
  106. var view = new View ();
  107. view.HotKeyBindings.Add (Key.A, Command.HotKey);
  108. view.KeyDownNotHandled += (s, e) => { Assert.Fail (); };
  109. var superView = new View ();
  110. superView.Add (view);
  111. var ke = Key.A;
  112. superView.NewKeyDownEvent (ke);
  113. }
  114. [Fact]
  115. public void NewKeyDownEvent_Honors_HotKey_KeyBindings_SuperView ()
  116. {
  117. var view = new View ();
  118. view.HotKeyBindings.Add (Key.A, Command.HotKey);
  119. bool hotKeyInvoked = false;
  120. view.HandlingHotKey += (s, e) => { hotKeyInvoked = true; };
  121. bool notHandled = false;
  122. view.KeyDownNotHandled += (s, e) => { notHandled = true; };
  123. var superView = new View ();
  124. superView.Add (view);
  125. var ke = Key.A;
  126. superView.NewKeyDownEvent (ke);
  127. Assert.False (notHandled);
  128. Assert.True (hotKeyInvoked);
  129. }
  130. [Fact]
  131. public void NewKeyDownEvent_InNewKeyDownEvent_Invokes_HotKey_Command_With_SuperView ()
  132. {
  133. var superView = new View ()
  134. {
  135. CanFocus = true
  136. };
  137. var view1 = new View
  138. {
  139. HotKeySpecifier = (Rune)'^',
  140. Title = "view^1",
  141. CanFocus = true
  142. };
  143. var view2 = new View
  144. {
  145. HotKeySpecifier = (Rune)'^',
  146. Title = "view^2",
  147. CanFocus = true
  148. };
  149. superView.Add (view1, view2);
  150. superView.SetFocus ();
  151. Assert.True (view1.HasFocus);
  152. var ke = Key.D2;
  153. superView.NewKeyDownEvent (ke);
  154. Assert.True (view2.HasFocus);
  155. }
  156. [Fact]
  157. public void Set_RemovesOldKeyBindings ()
  158. {
  159. var view = new View ();
  160. view.HotKey = KeyCode.A;
  161. Assert.Equal (string.Empty, view.Title);
  162. Assert.Equal (KeyCode.A, view.HotKey);
  163. // Verify key bindings were set
  164. Command [] commands = view.HotKeyBindings.GetCommands (KeyCode.A);
  165. Assert.Contains (Command.HotKey, commands);
  166. commands = view.HotKeyBindings.GetCommands (KeyCode.A | KeyCode.ShiftMask);
  167. Assert.Contains (Command.HotKey, commands);
  168. commands = view.HotKeyBindings.GetCommands (KeyCode.A | KeyCode.AltMask);
  169. Assert.Contains (Command.HotKey, commands);
  170. commands = view.HotKeyBindings.GetCommands (KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask);
  171. Assert.Contains (Command.HotKey, commands);
  172. // Now set again
  173. view.HotKey = KeyCode.B;
  174. Assert.Equal (string.Empty, view.Title);
  175. Assert.Equal (KeyCode.B, view.HotKey);
  176. commands = view.HotKeyBindings.GetCommands (KeyCode.A);
  177. Assert.DoesNotContain (Command.HotKey, commands);
  178. commands = view.HotKeyBindings.GetCommands (KeyCode.A | KeyCode.ShiftMask);
  179. Assert.DoesNotContain (Command.HotKey, commands);
  180. commands = view.HotKeyBindings.GetCommands (KeyCode.A | KeyCode.AltMask);
  181. Assert.DoesNotContain (Command.HotKey, commands);
  182. commands = view.HotKeyBindings.GetCommands (KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask);
  183. Assert.DoesNotContain (Command.HotKey, commands);
  184. }
  185. [Theory]
  186. [InlineData (KeyCode.A)]
  187. [InlineData (KeyCode.A | KeyCode.ShiftMask)]
  188. [InlineData (KeyCode.D1)]
  189. [InlineData (KeyCode.D1 | KeyCode.ShiftMask)]
  190. [InlineData ((KeyCode)'!')]
  191. [InlineData ((KeyCode)'х')] // Cyrillic x
  192. [InlineData ((KeyCode)'你')] // Chinese ni
  193. [InlineData ((KeyCode)'ö')] // German o umlaut
  194. [InlineData (KeyCode.Null)]
  195. public void Set_Sets_WithValidKey (KeyCode key)
  196. {
  197. var view = new View ();
  198. view.HotKey = key;
  199. Assert.Equal (key, view.HotKey);
  200. }
  201. [Theory]
  202. [InlineData (KeyCode.A)]
  203. [InlineData (KeyCode.A | KeyCode.ShiftMask)]
  204. [InlineData (KeyCode.D1)]
  205. [InlineData (KeyCode.D1 | KeyCode.ShiftMask)] // '!'
  206. [InlineData ((KeyCode)'х')] // Cyrillic x
  207. [InlineData ((KeyCode)'你')] // Chinese ni
  208. [InlineData ((KeyCode)'ö')] // German o umlaut
  209. public void Set_SetsKeyBindings (KeyCode key)
  210. {
  211. var view = new View ();
  212. view.HotKey = key;
  213. Assert.Equal (string.Empty, view.Title);
  214. Assert.Equal (key, view.HotKey);
  215. // Verify key bindings were set
  216. // As passed
  217. Command [] commands = view.HotKeyBindings.GetCommands (view.HotKey);
  218. Assert.Contains (Command.HotKey, commands);
  219. Key baseKey = view.HotKey.NoShift;
  220. // If A...Z, with and without shift
  221. if (baseKey.IsKeyCodeAtoZ)
  222. {
  223. commands = view.HotKeyBindings.GetCommands (view.HotKey.WithShift);
  224. Assert.Contains (Command.HotKey, commands);
  225. commands = view.HotKeyBindings.GetCommands (view.HotKey.NoShift);
  226. Assert.Contains (Command.HotKey, commands);
  227. commands = view.HotKeyBindings.GetCommands (view.HotKey.WithAlt);
  228. Assert.Contains (Command.HotKey, commands);
  229. commands = view.HotKeyBindings.GetCommands (view.HotKey.NoShift.WithAlt);
  230. Assert.Contains (Command.HotKey, commands);
  231. }
  232. else
  233. {
  234. // Non A..Z keys should not have shift bindings
  235. if (view.HotKey.IsShift)
  236. {
  237. commands = view.HotKeyBindings.GetCommands (view.HotKey.NoShift);
  238. Assert.Empty (commands);
  239. }
  240. else
  241. {
  242. commands = view.HotKeyBindings.GetCommands (view.HotKey.WithShift);
  243. Assert.Empty (commands);
  244. }
  245. }
  246. }
  247. [Fact]
  248. public void Set_Throws_If_Modifiers_Are_Included ()
  249. {
  250. var view = new View ();
  251. // A..Z must be naked (Alt is assumed)
  252. view.HotKey = Key.A.WithAlt;
  253. Assert.Throws<ArgumentException> (() => view.HotKey = Key.A.WithCtrl);
  254. Assert.Throws<ArgumentException> (
  255. () =>
  256. view.HotKey =
  257. KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask
  258. );
  259. // All others must not have Ctrl (Alt is assumed)
  260. view.HotKey = Key.D1.WithAlt;
  261. Assert.Throws<ArgumentException> (() => view.HotKey = Key.D1.WithCtrl);
  262. Assert.Throws<ArgumentException> (
  263. () =>
  264. view.HotKey =
  265. KeyCode.D1 | KeyCode.ShiftMask | KeyCode.AltMask | KeyCode.CtrlMask
  266. );
  267. // Shift is ok (e.g. this is '!')
  268. view.HotKey = Key.D1.WithShift;
  269. }
  270. [Theory]
  271. [InlineData (KeyCode.Delete)]
  272. [InlineData (KeyCode.Backspace)]
  273. [InlineData (KeyCode.Tab)]
  274. [InlineData (KeyCode.Enter)]
  275. [InlineData (KeyCode.Esc)]
  276. [InlineData (KeyCode.Space)]
  277. [InlineData (KeyCode.CursorLeft)]
  278. [InlineData (KeyCode.F1)]
  279. [InlineData (KeyCode.Null | KeyCode.ShiftMask)]
  280. public void Set_Throws_With_Invalid_Key (KeyCode key)
  281. {
  282. var view = new View ();
  283. Assert.Throws<ArgumentException> (() => view.HotKey = key);
  284. }
  285. [Theory]
  286. [InlineData ("Test", KeyCode.Null)]
  287. [InlineData ("^Test", KeyCode.T)]
  288. [InlineData ("T^est", KeyCode.E)]
  289. [InlineData ("Te^st", KeyCode.S)]
  290. [InlineData ("Tes^t", KeyCode.T)]
  291. [InlineData ("other", KeyCode.Null)]
  292. [InlineData ("oTher", KeyCode.Null)]
  293. [InlineData ("^Öther", (KeyCode)'Ö')]
  294. [InlineData ("^öther", (KeyCode)'ö')]
  295. // BUGBUG: '!' should be supported. Line 968 of TextFormatter filters on char.IsLetterOrDigit
  296. //[InlineData ("Test^!", (Key)'!')]
  297. public void Title_Change_Sets_HotKey (string title, KeyCode expectedHotKey)
  298. {
  299. var view = new View { HotKeySpecifier = new Rune ('^'), Title = "^Hello" };
  300. Assert.Equal (KeyCode.H, view.HotKey);
  301. view.Title = title;
  302. Assert.Equal (expectedHotKey, view.HotKey);
  303. }
  304. [Theory]
  305. [InlineData ("^Test")]
  306. public void Title_Empty_Sets_HotKey_To_Null (string title)
  307. {
  308. var view = new View { HotKeySpecifier = (Rune)'^', Title = title };
  309. Assert.Equal (title, view.Title);
  310. Assert.Equal (KeyCode.T, view.HotKey);
  311. view.Title = string.Empty;
  312. Assert.Equal ("", view.Title);
  313. Assert.Equal (KeyCode.Null, view.HotKey);
  314. }
  315. [Fact]
  316. public void HotKey_Raises_HotKeyCommand ()
  317. {
  318. var hotKeyRaised = false;
  319. var acceptRaised = false;
  320. var selectRaised = false;
  321. Application.Top = new Toplevel ();
  322. var view = new View
  323. {
  324. CanFocus = true,
  325. HotKeySpecifier = new Rune ('_'),
  326. Title = "_Test"
  327. };
  328. Application.Top.Add (view);
  329. view.HandlingHotKey += (s, e) => hotKeyRaised = true;
  330. view.Accepting += (s, e) => acceptRaised = true;
  331. view.Selecting += (s, e) => selectRaised = true;
  332. Assert.Equal (KeyCode.T, view.HotKey);
  333. Assert.True (Application.RaiseKeyDownEvent (Key.T));
  334. Assert.True (hotKeyRaised);
  335. Assert.False (acceptRaised);
  336. Assert.False (selectRaised);
  337. hotKeyRaised = false;
  338. Assert.True (Application.RaiseKeyDownEvent (Key.T.WithAlt));
  339. Assert.True (hotKeyRaised);
  340. Assert.False (acceptRaised);
  341. Assert.False (selectRaised);
  342. hotKeyRaised = false;
  343. view.HotKey = KeyCode.E;
  344. Assert.True (Application.RaiseKeyDownEvent (Key.E.WithAlt));
  345. Assert.True (hotKeyRaised);
  346. Assert.False (acceptRaised);
  347. Assert.False (selectRaised);
  348. Application.Top.Dispose ();
  349. Application.ResetState (true);
  350. }
  351. }