KeyboardTests.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  1. #nullable enable
  2. using Terminal.Gui.App;
  3. namespace ApplicationTests;
  4. /// <summary>
  5. /// Parallelizable tests for keyboard handling.
  6. /// These tests use isolated instances of <see cref="IKeyboard"/> to avoid static state dependencies.
  7. /// </summary>
  8. public class KeyboardTests
  9. {
  10. [Fact]
  11. public void Constructor_InitializesKeyBindings ()
  12. {
  13. // Arrange & Act
  14. var keyboard = new KeyboardImpl ();
  15. // Assert
  16. Assert.NotNull (keyboard.KeyBindings);
  17. // Verify that some default bindings exist
  18. Assert.True (keyboard.KeyBindings.TryGet (keyboard.QuitKey, out _));
  19. }
  20. [Fact]
  21. public void QuitKey_DefaultValue_IsEsc ()
  22. {
  23. // Arrange
  24. var keyboard = new KeyboardImpl ();
  25. // Assert
  26. Assert.Equal (Key.Esc, keyboard.QuitKey);
  27. }
  28. [Fact]
  29. public void QuitKey_SetValue_UpdatesKeyBindings ()
  30. {
  31. // Arrange
  32. var keyboard = new KeyboardImpl ();
  33. Key newQuitKey = Key.Q.WithCtrl;
  34. // Act
  35. keyboard.QuitKey = newQuitKey;
  36. // Assert
  37. Assert.Equal (newQuitKey, keyboard.QuitKey);
  38. Assert.True (keyboard.KeyBindings.TryGet (newQuitKey, out KeyBinding binding));
  39. Assert.Contains (Command.Quit, binding.Commands);
  40. }
  41. [Fact]
  42. public void ArrangeKey_DefaultValue_IsCtrlF5 ()
  43. {
  44. // Arrange
  45. var keyboard = new KeyboardImpl ();
  46. // Assert
  47. Assert.Equal (Key.F5.WithCtrl, keyboard.ArrangeKey);
  48. }
  49. [Fact]
  50. public void NextTabKey_DefaultValue_IsTab ()
  51. {
  52. // Arrange
  53. var keyboard = new KeyboardImpl ();
  54. // Assert
  55. Assert.Equal (Key.Tab, keyboard.NextTabKey);
  56. }
  57. [Fact]
  58. public void PrevTabKey_DefaultValue_IsShiftTab ()
  59. {
  60. // Arrange
  61. var keyboard = new KeyboardImpl ();
  62. // Assert
  63. Assert.Equal (Key.Tab.WithShift, keyboard.PrevTabKey);
  64. }
  65. [Fact]
  66. public void NextTabGroupKey_DefaultValue_IsF6 ()
  67. {
  68. // Arrange
  69. var keyboard = new KeyboardImpl ();
  70. // Assert
  71. Assert.Equal (Key.F6, keyboard.NextTabGroupKey);
  72. }
  73. [Fact]
  74. public void PrevTabGroupKey_DefaultValue_IsShiftF6 ()
  75. {
  76. // Arrange
  77. var keyboard = new KeyboardImpl ();
  78. // Assert
  79. Assert.Equal (Key.F6.WithShift, keyboard.PrevTabGroupKey);
  80. }
  81. [Fact]
  82. public void KeyBindings_Add_CanAddCustomBinding ()
  83. {
  84. // Arrange
  85. var keyboard = new KeyboardImpl ();
  86. Key customKey = Key.K.WithCtrl;
  87. // Act
  88. keyboard.KeyBindings.Add (customKey, Command.Accept);
  89. // Assert
  90. Assert.True (keyboard.KeyBindings.TryGet (customKey, out KeyBinding binding));
  91. Assert.Contains (Command.Accept, binding.Commands);
  92. }
  93. [Fact]
  94. public void KeyBindings_Remove_CanRemoveBinding ()
  95. {
  96. // Arrange
  97. var keyboard = new KeyboardImpl ();
  98. Key customKey = Key.K.WithCtrl;
  99. keyboard.KeyBindings.Add (customKey, Command.Accept);
  100. // Act
  101. keyboard.KeyBindings.Remove (customKey);
  102. // Assert
  103. Assert.False (keyboard.KeyBindings.TryGet (customKey, out _));
  104. }
  105. [Fact]
  106. public void KeyDown_Event_CanBeSubscribed ()
  107. {
  108. // Arrange
  109. var keyboard = new KeyboardImpl ();
  110. bool eventRaised = false;
  111. // Act
  112. keyboard.KeyDown += (sender, key) =>
  113. {
  114. eventRaised = true;
  115. };
  116. // Assert - event subscription doesn't throw
  117. Assert.False (eventRaised); // Event hasn't been raised yet
  118. }
  119. [Fact]
  120. public void KeyUp_Event_CanBeSubscribed ()
  121. {
  122. // Arrange
  123. var keyboard = new KeyboardImpl ();
  124. bool eventRaised = false;
  125. // Act
  126. keyboard.KeyUp += (sender, key) =>
  127. {
  128. eventRaised = true;
  129. };
  130. // Assert - event subscription doesn't throw
  131. Assert.False (eventRaised); // Event hasn't been raised yet
  132. }
  133. [Fact]
  134. public void InvokeCommand_WithInvalidCommand_ThrowsNotSupportedException ()
  135. {
  136. // Arrange
  137. var keyboard = new KeyboardImpl ();
  138. // Pick a command that isn't registered
  139. Command invalidCommand = (Command)9999;
  140. Key testKey = Key.A;
  141. var binding = new KeyBinding ([invalidCommand]);
  142. // Act & Assert
  143. Assert.Throws<NotSupportedException> (() => keyboard.InvokeCommand (invalidCommand, testKey, binding));
  144. }
  145. [Fact]
  146. public void Multiple_Keyboards_CanExistIndependently ()
  147. {
  148. // Arrange & Act
  149. var keyboard1 = new KeyboardImpl ();
  150. var keyboard2 = new KeyboardImpl ();
  151. keyboard1.QuitKey = Key.Q.WithCtrl;
  152. keyboard2.QuitKey = Key.X.WithCtrl;
  153. // Assert - each keyboard maintains independent state
  154. Assert.Equal (Key.Q.WithCtrl, keyboard1.QuitKey);
  155. Assert.Equal (Key.X.WithCtrl, keyboard2.QuitKey);
  156. Assert.NotEqual (keyboard1.QuitKey, keyboard2.QuitKey);
  157. }
  158. [Fact]
  159. public void KeyBindings_Replace_UpdatesExistingBinding ()
  160. {
  161. // Arrange
  162. var keyboard = new KeyboardImpl ();
  163. Key oldKey = Key.Esc;
  164. Key newKey = Key.Q.WithCtrl;
  165. // Verify initial state
  166. Assert.True (keyboard.KeyBindings.TryGet (oldKey, out KeyBinding oldBinding));
  167. Assert.Contains (Command.Quit, oldBinding.Commands);
  168. // Act
  169. keyboard.KeyBindings.Replace (oldKey, newKey);
  170. // Assert - old key should no longer have the binding
  171. Assert.False (keyboard.KeyBindings.TryGet (oldKey, out _));
  172. // New key should have the binding
  173. Assert.True (keyboard.KeyBindings.TryGet (newKey, out KeyBinding newBinding));
  174. Assert.Contains (Command.Quit, newBinding.Commands);
  175. }
  176. [Fact]
  177. public void KeyBindings_Clear_RemovesAllBindings ()
  178. {
  179. // Arrange
  180. var keyboard = new KeyboardImpl ();
  181. // Verify initial state has bindings
  182. Assert.True (keyboard.KeyBindings.TryGet (keyboard.QuitKey, out _));
  183. // Act
  184. keyboard.KeyBindings.Clear ();
  185. // Assert - previously existing binding is gone
  186. Assert.False (keyboard.KeyBindings.TryGet (keyboard.QuitKey, out _));
  187. }
  188. [Fact]
  189. public void AddKeyBindings_PopulatesDefaultBindings ()
  190. {
  191. // Arrange
  192. var keyboard = new KeyboardImpl ();
  193. keyboard.KeyBindings.Clear ();
  194. Assert.False (keyboard.KeyBindings.TryGet (keyboard.QuitKey, out _));
  195. // Act
  196. keyboard.AddKeyBindings ();
  197. // Assert
  198. Assert.True (keyboard.KeyBindings.TryGet (keyboard.QuitKey, out KeyBinding binding));
  199. Assert.Contains (Command.Quit, binding.Commands);
  200. }
  201. // Migrated from UnitTests/Application/KeyboardTests.cs
  202. [Fact]
  203. public void KeyBindings_Add_Adds ()
  204. {
  205. // Arrange
  206. var keyboard = new KeyboardImpl ();
  207. // Act
  208. keyboard.KeyBindings.Add (Key.A, Command.Accept);
  209. keyboard.KeyBindings.Add (Key.B, Command.Accept);
  210. // Assert
  211. Assert.True (keyboard.KeyBindings.TryGet (Key.A, out KeyBinding binding));
  212. Assert.Null (binding.Target);
  213. Assert.True (keyboard.KeyBindings.TryGet (Key.B, out binding));
  214. Assert.Null (binding.Target);
  215. }
  216. [Fact]
  217. public void KeyBindings_Remove_Removes ()
  218. {
  219. // Arrange
  220. var keyboard = new KeyboardImpl ();
  221. keyboard.KeyBindings.Add (Key.A, Command.Accept);
  222. Assert.True (keyboard.KeyBindings.TryGet (Key.A, out _));
  223. // Act
  224. keyboard.KeyBindings.Remove (Key.A);
  225. // Assert
  226. Assert.False (keyboard.KeyBindings.TryGet (Key.A, out _));
  227. }
  228. [Fact]
  229. public void QuitKey_Default_Is_Esc ()
  230. {
  231. // Arrange & Act
  232. var keyboard = new KeyboardImpl ();
  233. // Assert
  234. Assert.Equal (Key.Esc, keyboard.QuitKey);
  235. }
  236. [Fact]
  237. public void QuitKey_Setter_UpdatesBindings ()
  238. {
  239. // Arrange
  240. var keyboard = new KeyboardImpl ();
  241. Key prevKey = keyboard.QuitKey;
  242. // Act - Change QuitKey
  243. keyboard.QuitKey = Key.C.WithCtrl;
  244. // Assert - Old key should no longer trigger quit
  245. Assert.False (keyboard.KeyBindings.TryGet (prevKey, out _));
  246. // New key should trigger quit
  247. Assert.True (keyboard.KeyBindings.TryGet (Key.C.WithCtrl, out KeyBinding binding));
  248. Assert.Contains (Command.Quit, binding.Commands);
  249. }
  250. [Fact]
  251. public void NextTabKey_Setter_UpdatesBindings ()
  252. {
  253. // Arrange
  254. var keyboard = new KeyboardImpl ();
  255. Key prevKey = keyboard.NextTabKey;
  256. Key newKey = Key.N.WithCtrl;
  257. // Act
  258. keyboard.NextTabKey = newKey;
  259. // Assert
  260. Assert.Equal (newKey, keyboard.NextTabKey);
  261. Assert.True (keyboard.KeyBindings.TryGet (newKey, out KeyBinding binding));
  262. Assert.Contains (Command.NextTabStop, binding.Commands);
  263. }
  264. [Fact]
  265. public void PrevTabKey_Setter_UpdatesBindings ()
  266. {
  267. // Arrange
  268. var keyboard = new KeyboardImpl ();
  269. Key newKey = Key.P.WithCtrl;
  270. // Act
  271. keyboard.PrevTabKey = newKey;
  272. // Assert
  273. Assert.Equal (newKey, keyboard.PrevTabKey);
  274. Assert.True (keyboard.KeyBindings.TryGet (newKey, out KeyBinding binding));
  275. Assert.Contains (Command.PreviousTabStop, binding.Commands);
  276. }
  277. [Fact]
  278. public void NextTabGroupKey_Setter_UpdatesBindings ()
  279. {
  280. // Arrange
  281. var keyboard = new KeyboardImpl ();
  282. Key newKey = Key.PageDown.WithCtrl;
  283. // Act
  284. keyboard.NextTabGroupKey = newKey;
  285. // Assert
  286. Assert.Equal (newKey, keyboard.NextTabGroupKey);
  287. Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, keyboard.NextTabGroupKey.KeyCode);
  288. Assert.True (keyboard.KeyBindings.TryGet (newKey, out KeyBinding binding));
  289. Assert.Contains (Command.NextTabGroup, binding.Commands);
  290. }
  291. [Fact]
  292. public void PrevTabGroupKey_Setter_UpdatesBindings ()
  293. {
  294. // Arrange
  295. var keyboard = new KeyboardImpl ();
  296. Key newKey = Key.PageUp.WithCtrl;
  297. // Act
  298. keyboard.PrevTabGroupKey = newKey;
  299. // Assert
  300. Assert.Equal (newKey, keyboard.PrevTabGroupKey);
  301. Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, keyboard.PrevTabGroupKey.KeyCode);
  302. Assert.True (keyboard.KeyBindings.TryGet (newKey, out KeyBinding binding));
  303. Assert.Contains (Command.PreviousTabGroup, binding.Commands);
  304. }
  305. [Fact]
  306. public void ArrangeKey_Setter_UpdatesBindings ()
  307. {
  308. // Arrange
  309. var keyboard = new KeyboardImpl ();
  310. Key newKey = Key.A.WithCtrl;
  311. // Act
  312. keyboard.ArrangeKey = newKey;
  313. // Assert
  314. Assert.Equal (newKey, keyboard.ArrangeKey);
  315. Assert.True (keyboard.KeyBindings.TryGet (newKey, out KeyBinding binding));
  316. Assert.Contains (Command.Arrange, binding.Commands);
  317. }
  318. [Fact]
  319. public void KeyBindings_AddWithTarget_StoresTarget ()
  320. {
  321. // Arrange
  322. var keyboard = new KeyboardImpl ();
  323. var view = new View ();
  324. // Act
  325. keyboard.KeyBindings.Add (Key.A.WithCtrl, view, Command.Accept);
  326. // Assert
  327. Assert.True (keyboard.KeyBindings.TryGet (Key.A.WithCtrl, out KeyBinding binding));
  328. Assert.Equal (view, binding.Target);
  329. Assert.Contains (Command.Accept, binding.Commands);
  330. view.Dispose ();
  331. }
  332. [Fact]
  333. public void InvokeCommandsBoundToKey_ReturnsNull_WhenNoBindingExists ()
  334. {
  335. // Arrange
  336. var keyboard = new KeyboardImpl ();
  337. Key unboundKey = Key.Z.WithAlt.WithCtrl;
  338. // Act
  339. bool? result = keyboard.InvokeCommandsBoundToKey (unboundKey);
  340. // Assert
  341. Assert.Null (result);
  342. }
  343. [Fact]
  344. public void InvokeCommandsBoundToKey_InvokesCommand_WhenBindingExists ()
  345. {
  346. // Arrange
  347. var keyboard = new KeyboardImpl ();
  348. // QuitKey has a bound command by default
  349. // Act
  350. bool? result = keyboard.InvokeCommandsBoundToKey (keyboard.QuitKey);
  351. // Assert
  352. // Command.Quit would normally call Application.RequestStop,
  353. // but in isolation it should return true (handled)
  354. Assert.NotNull (result);
  355. }
  356. [Fact]
  357. public void Multiple_Keyboards_Independent_KeyBindings ()
  358. {
  359. // Arrange
  360. var keyboard1 = new KeyboardImpl ();
  361. var keyboard2 = new KeyboardImpl ();
  362. // Act
  363. keyboard1.KeyBindings.Add (Key.X, Command.Accept);
  364. keyboard2.KeyBindings.Add (Key.Y, Command.Cancel);
  365. // Assert
  366. Assert.True (keyboard1.KeyBindings.TryGet (Key.X, out _));
  367. Assert.False (keyboard1.KeyBindings.TryGet (Key.Y, out _));
  368. Assert.True (keyboard2.KeyBindings.TryGet (Key.Y, out _));
  369. Assert.False (keyboard2.KeyBindings.TryGet (Key.X, out _));
  370. }
  371. [Fact]
  372. public void KeyBindings_Replace_PreservesCommandsForNewKey ()
  373. {
  374. // Arrange
  375. var keyboard = new KeyboardImpl ();
  376. Key oldKey = Key.Esc;
  377. Key newKey = Key.Q.WithCtrl;
  378. // Get the commands from the old binding
  379. Assert.True (keyboard.KeyBindings.TryGet (oldKey, out KeyBinding oldBinding));
  380. Command[] oldCommands = oldBinding.Commands.ToArray ();
  381. // Act
  382. keyboard.KeyBindings.Replace (oldKey, newKey);
  383. // Assert - new key should have the same commands
  384. Assert.True (keyboard.KeyBindings.TryGet (newKey, out KeyBinding newBinding));
  385. Assert.Equal (oldCommands, newBinding.Commands);
  386. }
  387. }