HotKeyTests.cs 13 KB

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