WindowsKeyConverterTests.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798
  1. using System.Runtime.InteropServices;
  2. namespace UnitTests_Parallelizable.DriverTests;
  3. [Collection ("Global Test Setup")]
  4. [Trait ("Platform", "Windows")]
  5. public class WindowsKeyConverterTests
  6. {
  7. private readonly WindowsKeyConverter _converter = new ();
  8. #region ToKey Tests - Basic Characters
  9. [Theory]
  10. [InlineData ('a', ConsoleKey.A, false, false, false, KeyCode.A)] // lowercase a
  11. [InlineData ('A', ConsoleKey.A, true, false, false, KeyCode.A | KeyCode.ShiftMask)] // uppercase A
  12. [InlineData ('z', ConsoleKey.Z, false, false, false, KeyCode.Z)]
  13. [InlineData ('Z', ConsoleKey.Z, true, false, false, KeyCode.Z | KeyCode.ShiftMask)]
  14. public void ToKey_LetterKeys_ReturnsExpectedKeyCode (
  15. char unicodeChar,
  16. ConsoleKey consoleKey,
  17. bool shift,
  18. bool alt,
  19. bool ctrl,
  20. KeyCode expectedKeyCode
  21. )
  22. {
  23. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  24. {
  25. return;
  26. }
  27. // Arrange
  28. WindowsConsole.InputRecord inputRecord = CreateInputRecord (unicodeChar, consoleKey, shift, alt, ctrl);
  29. // Act
  30. var result = _converter.ToKey (inputRecord);
  31. // Assert
  32. Assert.Equal (expectedKeyCode, result.KeyCode);
  33. }
  34. [Theory]
  35. [InlineData ('0', ConsoleKey.D0, false, false, false, KeyCode.D0)]
  36. [InlineData ('1', ConsoleKey.D1, false, false, false, KeyCode.D1)]
  37. [InlineData ('9', ConsoleKey.D9, false, false, false, KeyCode.D9)]
  38. public void ToKey_NumberKeys_ReturnsExpectedKeyCode (
  39. char unicodeChar,
  40. ConsoleKey consoleKey,
  41. bool shift,
  42. bool alt,
  43. bool ctrl,
  44. KeyCode expectedKeyCode
  45. )
  46. {
  47. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  48. {
  49. return;
  50. }
  51. // Arrange
  52. WindowsConsole.InputRecord inputRecord = CreateInputRecord (unicodeChar, consoleKey, shift, alt, ctrl);
  53. // Act
  54. var result = _converter.ToKey (inputRecord);
  55. // Assert
  56. Assert.Equal (expectedKeyCode, result.KeyCode);
  57. }
  58. #endregion
  59. #region ToKey Tests - Modifiers
  60. [Theory]
  61. [InlineData ('a', ConsoleKey.A, false, false, true, KeyCode.A | KeyCode.CtrlMask)] // Ctrl+A
  62. [InlineData ('A', ConsoleKey.A, true, false, true, KeyCode.A | KeyCode.ShiftMask | KeyCode.CtrlMask)] // Ctrl+Shift+A (Windows keeps ShiftMask)
  63. [InlineData ('a', ConsoleKey.A, false, true, false, KeyCode.A | KeyCode.AltMask)] // Alt+A
  64. [InlineData ('A', ConsoleKey.A, true, true, false, KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask)] // Alt+Shift+A
  65. [InlineData ('a', ConsoleKey.A, false, true, true, KeyCode.A | KeyCode.CtrlMask | KeyCode.AltMask)] // Ctrl+Alt+A
  66. public void ToKey_WithModifiers_ReturnsExpectedKeyCode (
  67. char unicodeChar,
  68. ConsoleKey consoleKey,
  69. bool shift,
  70. bool alt,
  71. bool ctrl,
  72. KeyCode expectedKeyCode
  73. )
  74. {
  75. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  76. {
  77. return;
  78. }
  79. // Arrange
  80. WindowsConsole.InputRecord inputRecord = CreateInputRecord (unicodeChar, consoleKey, shift, alt, ctrl);
  81. // Act
  82. var result = _converter.ToKey (inputRecord);
  83. // Assert
  84. Assert.Equal (expectedKeyCode, result.KeyCode);
  85. }
  86. #endregion
  87. #region ToKey Tests - Special Keys
  88. [Theory]
  89. [InlineData (ConsoleKey.Enter, KeyCode.Enter)]
  90. [InlineData (ConsoleKey.Escape, KeyCode.Esc)]
  91. [InlineData (ConsoleKey.Tab, KeyCode.Tab)]
  92. [InlineData (ConsoleKey.Backspace, KeyCode.Backspace)]
  93. [InlineData (ConsoleKey.Delete, KeyCode.Delete)]
  94. [InlineData (ConsoleKey.Insert, KeyCode.Insert)]
  95. [InlineData (ConsoleKey.Home, KeyCode.Home)]
  96. [InlineData (ConsoleKey.End, KeyCode.End)]
  97. [InlineData (ConsoleKey.PageUp, KeyCode.PageUp)]
  98. [InlineData (ConsoleKey.PageDown, KeyCode.PageDown)]
  99. [InlineData (ConsoleKey.UpArrow, KeyCode.CursorUp)]
  100. [InlineData (ConsoleKey.DownArrow, KeyCode.CursorDown)]
  101. [InlineData (ConsoleKey.LeftArrow, KeyCode.CursorLeft)]
  102. [InlineData (ConsoleKey.RightArrow, KeyCode.CursorRight)]
  103. public void ToKey_SpecialKeys_ReturnsExpectedKeyCode (ConsoleKey consoleKey, KeyCode expectedKeyCode)
  104. {
  105. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  106. {
  107. return;
  108. }
  109. // Arrange
  110. char unicodeChar = consoleKey switch
  111. {
  112. ConsoleKey.Enter => '\r',
  113. ConsoleKey.Escape => '\u001B',
  114. ConsoleKey.Tab => '\t',
  115. ConsoleKey.Backspace => '\b',
  116. _ => '\0'
  117. };
  118. WindowsConsole.InputRecord inputRecord = CreateInputRecord (unicodeChar, consoleKey, false, false, false);
  119. // Act
  120. var result = _converter.ToKey (inputRecord);
  121. // Assert
  122. Assert.Equal (expectedKeyCode, result.KeyCode);
  123. }
  124. [Theory]
  125. [InlineData (ConsoleKey.F1, KeyCode.F1)]
  126. [InlineData (ConsoleKey.F2, KeyCode.F2)]
  127. [InlineData (ConsoleKey.F3, KeyCode.F3)]
  128. [InlineData (ConsoleKey.F4, KeyCode.F4)]
  129. [InlineData (ConsoleKey.F5, KeyCode.F5)]
  130. [InlineData (ConsoleKey.F6, KeyCode.F6)]
  131. [InlineData (ConsoleKey.F7, KeyCode.F7)]
  132. [InlineData (ConsoleKey.F8, KeyCode.F8)]
  133. [InlineData (ConsoleKey.F9, KeyCode.F9)]
  134. [InlineData (ConsoleKey.F10, KeyCode.F10)]
  135. [InlineData (ConsoleKey.F11, KeyCode.F11)]
  136. [InlineData (ConsoleKey.F12, KeyCode.F12)]
  137. public void ToKey_FunctionKeys_ReturnsExpectedKeyCode (ConsoleKey consoleKey, KeyCode expectedKeyCode)
  138. {
  139. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  140. {
  141. return;
  142. }
  143. // Arrange
  144. WindowsConsole.InputRecord inputRecord = CreateInputRecord ('\0', consoleKey, false, false, false);
  145. // Act
  146. var result = _converter.ToKey (inputRecord);
  147. // Assert
  148. Assert.Equal (expectedKeyCode, result.KeyCode);
  149. }
  150. #endregion
  151. #region ToKey Tests - VK_PACKET (Unicode/IME)
  152. [Theory]
  153. [InlineData ('中')] // Chinese character
  154. [InlineData ('日')] // Japanese character
  155. [InlineData ('한')] // Korean character
  156. [InlineData ('é')] // Accented character
  157. [InlineData ('€')] // Euro symbol
  158. [InlineData ('Ω')] // Greek character
  159. public void ToKey_VKPacket_Unicode_ReturnsExpectedCharacter (char unicodeChar)
  160. {
  161. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  162. {
  163. return;
  164. }
  165. // Arrange
  166. WindowsConsole.InputRecord inputRecord = CreateVKPacketInputRecord (unicodeChar);
  167. // Act
  168. var result = _converter.ToKey (inputRecord);
  169. // Assert
  170. Assert.Equal ((KeyCode)unicodeChar, result.KeyCode);
  171. }
  172. [Fact]
  173. public void ToKey_VKPacket_ZeroChar_ReturnsNull ()
  174. {
  175. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  176. {
  177. return;
  178. }
  179. // Arrange
  180. WindowsConsole.InputRecord inputRecord = CreateVKPacketInputRecord ('\0');
  181. // Act
  182. var result = _converter.ToKey (inputRecord);
  183. // Assert
  184. Assert.Equal (KeyCode.Null, result.KeyCode);
  185. }
  186. [Fact]
  187. public void ToKey_VKPacket_SurrogatePair_DocumentsCurrentLimitation ()
  188. {
  189. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  190. {
  191. return;
  192. }
  193. // Emoji '😀' (U+1F600) requires a surrogate pair: High=U+D83D, Low=U+DE00
  194. // Windows sends this as TWO consecutive VK_PACKET events (one for each char)
  195. // because KeyEventRecord.UnicodeChar is a single 16-bit char field.
  196. //
  197. // CURRENT LIMITATION: WindowsKeyConverter processes each event independently
  198. // and does not combine surrogate pairs into a single Rune/KeyCode.
  199. // This test documents the current (incorrect) behavior.
  200. //
  201. // TODO: Implement proper surrogate pair handling at the InputProcessor level
  202. // to combine consecutive high+low surrogate events into a single Key with the
  203. // complete Unicode codepoint.
  204. // See: https://docs.microsoft.com/en-us/windows/console/key-event-record
  205. var highSurrogate = '\uD83D'; // High surrogate for 😀
  206. var lowSurrogate = '\uDE00'; // Low surrogate for 😀
  207. // First event with high surrogate
  208. WindowsConsole.InputRecord highRecord = CreateVKPacketInputRecord (highSurrogate);
  209. var highResult = _converter.ToKey (highRecord);
  210. // Second event with low surrogate
  211. WindowsConsole.InputRecord lowRecord = CreateVKPacketInputRecord (lowSurrogate);
  212. var lowResult = _converter.ToKey (lowRecord);
  213. // Currently each surrogate half is processed independently as invalid KeyCodes
  214. // These assertions document the current (broken) behavior
  215. Assert.Equal ((KeyCode)highSurrogate, highResult.KeyCode);
  216. Assert.Equal ((KeyCode)lowSurrogate, lowResult.KeyCode);
  217. // What SHOULD happen (future fix):
  218. // The InputProcessor should detect the surrogate pair and combine them:
  219. // var expectedRune = new Rune(0x1F600); // 😀
  220. // Assert.Equal((KeyCode)expectedRune.Value, combinedResult.KeyCode);
  221. }
  222. #endregion
  223. #region ToKey Tests - OEM Keys
  224. [Theory]
  225. [InlineData (';', ConsoleKey.Oem1, false, (KeyCode)';')]
  226. [InlineData (':', ConsoleKey.Oem1, true, (KeyCode)':')]
  227. [InlineData ('/', ConsoleKey.Oem2, false, (KeyCode)'/')]
  228. [InlineData ('?', ConsoleKey.Oem2, true, (KeyCode)'?')]
  229. [InlineData (',', ConsoleKey.OemComma, false, (KeyCode)',')]
  230. [InlineData ('<', ConsoleKey.OemComma, true, (KeyCode)'<')]
  231. [InlineData ('.', ConsoleKey.OemPeriod, false, (KeyCode)'.')]
  232. [InlineData ('>', ConsoleKey.OemPeriod, true, (KeyCode)'>')]
  233. [InlineData ('=', ConsoleKey.OemPlus, false, (KeyCode)'=')] // Un-shifted OemPlus is '='
  234. [InlineData ('+', ConsoleKey.OemPlus, true, (KeyCode)'+')] // Shifted OemPlus is '+'
  235. [InlineData ('-', ConsoleKey.OemMinus, false, (KeyCode)'-')]
  236. [InlineData ('_', ConsoleKey.OemMinus, true, (KeyCode)'_')] // Shifted OemMinus is '_'
  237. public void ToKey_OEMKeys_ReturnsExpectedKeyCode (
  238. char unicodeChar,
  239. ConsoleKey consoleKey,
  240. bool shift,
  241. KeyCode expectedKeyCode
  242. )
  243. {
  244. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  245. {
  246. return;
  247. }
  248. // Arrange
  249. WindowsConsole.InputRecord inputRecord = CreateInputRecord (unicodeChar, consoleKey, shift, false, false);
  250. // Act
  251. var result = _converter.ToKey (inputRecord);
  252. // Assert
  253. Assert.Equal (expectedKeyCode, result.KeyCode);
  254. }
  255. #endregion
  256. #region ToKey Tests - NumPad
  257. [Theory]
  258. [InlineData ('0', ConsoleKey.NumPad0, KeyCode.D0)]
  259. [InlineData ('1', ConsoleKey.NumPad1, KeyCode.D1)]
  260. [InlineData ('5', ConsoleKey.NumPad5, KeyCode.D5)]
  261. [InlineData ('9', ConsoleKey.NumPad9, KeyCode.D9)]
  262. public void ToKey_NumPadKeys_ReturnsExpectedKeyCode (
  263. char unicodeChar,
  264. ConsoleKey consoleKey,
  265. KeyCode expectedKeyCode
  266. )
  267. {
  268. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  269. {
  270. return;
  271. }
  272. // Arrange
  273. WindowsConsole.InputRecord inputRecord = CreateInputRecord (unicodeChar, consoleKey, false, false, false);
  274. // Act
  275. var result = _converter.ToKey (inputRecord);
  276. // Assert
  277. Assert.Equal (expectedKeyCode, result.KeyCode);
  278. }
  279. [Theory]
  280. [InlineData ('*', ConsoleKey.Multiply, (KeyCode)'*')]
  281. [InlineData ('+', ConsoleKey.Add, (KeyCode)'+')]
  282. [InlineData ('-', ConsoleKey.Subtract, (KeyCode)'-')]
  283. [InlineData ('.', ConsoleKey.Decimal, (KeyCode)'.')]
  284. [InlineData ('/', ConsoleKey.Divide, (KeyCode)'/')]
  285. public void ToKey_NumPadOperators_ReturnsExpectedKeyCode (
  286. char unicodeChar,
  287. ConsoleKey consoleKey,
  288. KeyCode expectedKeyCode
  289. )
  290. {
  291. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  292. {
  293. return;
  294. }
  295. // Arrange
  296. WindowsConsole.InputRecord inputRecord = CreateInputRecord (unicodeChar, consoleKey, false, false, false);
  297. // Act
  298. var result = _converter.ToKey (inputRecord);
  299. // Assert
  300. Assert.Equal (expectedKeyCode, result.KeyCode);
  301. }
  302. #endregion
  303. #region ToKey Tests - Null/Empty
  304. [Fact]
  305. public void ToKey_NullKey_ReturnsEmpty ()
  306. {
  307. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  308. {
  309. return;
  310. }
  311. // Arrange
  312. WindowsConsole.InputRecord inputRecord = CreateInputRecord ('\0', ConsoleKey.None, false, false, false);
  313. // Act
  314. var result = _converter.ToKey (inputRecord);
  315. // Assert
  316. Assert.Equal (Key.Empty, result);
  317. }
  318. #endregion
  319. #region ToKeyInfo Tests - Basic Keys
  320. [Theory]
  321. [InlineData (KeyCode.A, ConsoleKey.A, 'a')]
  322. [InlineData (KeyCode.A | KeyCode.ShiftMask, ConsoleKey.A, 'A')]
  323. [InlineData (KeyCode.Z, ConsoleKey.Z, 'z')]
  324. [InlineData (KeyCode.Z | KeyCode.ShiftMask, ConsoleKey.Z, 'Z')]
  325. public void ToKeyInfo_LetterKeys_ReturnsExpectedInputRecord (
  326. KeyCode keyCode,
  327. ConsoleKey expectedConsoleKey,
  328. char expectedChar
  329. )
  330. {
  331. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  332. {
  333. return;
  334. }
  335. // Arrange
  336. var key = new Key (keyCode);
  337. // Act
  338. WindowsConsole.InputRecord result = _converter.ToKeyInfo (key);
  339. // Assert
  340. Assert.Equal (WindowsConsole.EventType.Key, result.EventType);
  341. Assert.Equal ((VK)expectedConsoleKey, result.KeyEvent.wVirtualKeyCode);
  342. Assert.Equal (expectedChar, result.KeyEvent.UnicodeChar);
  343. Assert.True (result.KeyEvent.bKeyDown);
  344. Assert.Equal ((ushort)1, result.KeyEvent.wRepeatCount);
  345. }
  346. [Theory]
  347. [InlineData (KeyCode.D0, ConsoleKey.D0, '0')]
  348. [InlineData (KeyCode.D1, ConsoleKey.D1, '1')]
  349. [InlineData (KeyCode.D9, ConsoleKey.D9, '9')]
  350. public void ToKeyInfo_NumberKeys_ReturnsExpectedInputRecord (
  351. KeyCode keyCode,
  352. ConsoleKey expectedConsoleKey,
  353. char expectedChar
  354. )
  355. {
  356. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  357. {
  358. return;
  359. }
  360. // Arrange
  361. var key = new Key (keyCode);
  362. // Act
  363. WindowsConsole.InputRecord result = _converter.ToKeyInfo (key);
  364. // Assert
  365. Assert.Equal ((VK)expectedConsoleKey, result.KeyEvent.wVirtualKeyCode);
  366. Assert.Equal (expectedChar, result.KeyEvent.UnicodeChar);
  367. }
  368. #endregion
  369. #region ToKeyInfo Tests - Special Keys
  370. [Theory]
  371. [InlineData (KeyCode.Enter, ConsoleKey.Enter, '\r')]
  372. [InlineData (KeyCode.Esc, ConsoleKey.Escape, '\u001B')]
  373. [InlineData (KeyCode.Tab, ConsoleKey.Tab, '\t')]
  374. [InlineData (KeyCode.Backspace, ConsoleKey.Backspace, '\b')]
  375. [InlineData (KeyCode.Space, ConsoleKey.Spacebar, ' ')]
  376. public void ToKeyInfo_SpecialKeys_ReturnsExpectedInputRecord (
  377. KeyCode keyCode,
  378. ConsoleKey expectedConsoleKey,
  379. char expectedChar
  380. )
  381. {
  382. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  383. {
  384. return;
  385. }
  386. // Arrange
  387. var key = new Key (keyCode);
  388. // Act
  389. WindowsConsole.InputRecord result = _converter.ToKeyInfo (key);
  390. // Assert
  391. Assert.Equal ((VK)expectedConsoleKey, result.KeyEvent.wVirtualKeyCode);
  392. Assert.Equal (expectedChar, result.KeyEvent.UnicodeChar);
  393. }
  394. [Theory]
  395. [InlineData (KeyCode.Delete, ConsoleKey.Delete)]
  396. [InlineData (KeyCode.Insert, ConsoleKey.Insert)]
  397. [InlineData (KeyCode.Home, ConsoleKey.Home)]
  398. [InlineData (KeyCode.End, ConsoleKey.End)]
  399. [InlineData (KeyCode.PageUp, ConsoleKey.PageUp)]
  400. [InlineData (KeyCode.PageDown, ConsoleKey.PageDown)]
  401. [InlineData (KeyCode.CursorUp, ConsoleKey.UpArrow)]
  402. [InlineData (KeyCode.CursorDown, ConsoleKey.DownArrow)]
  403. [InlineData (KeyCode.CursorLeft, ConsoleKey.LeftArrow)]
  404. [InlineData (KeyCode.CursorRight, ConsoleKey.RightArrow)]
  405. public void ToKeyInfo_NavigationKeys_ReturnsExpectedInputRecord (KeyCode keyCode, ConsoleKey expectedConsoleKey)
  406. {
  407. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  408. {
  409. return;
  410. }
  411. // Arrange
  412. var key = new Key (keyCode);
  413. // Act
  414. WindowsConsole.InputRecord result = _converter.ToKeyInfo (key);
  415. // Assert
  416. Assert.Equal ((VK)expectedConsoleKey, result.KeyEvent.wVirtualKeyCode);
  417. }
  418. [Theory]
  419. [InlineData (KeyCode.F1, ConsoleKey.F1)]
  420. [InlineData (KeyCode.F5, ConsoleKey.F5)]
  421. [InlineData (KeyCode.F10, ConsoleKey.F10)]
  422. [InlineData (KeyCode.F12, ConsoleKey.F12)]
  423. public void ToKeyInfo_FunctionKeys_ReturnsExpectedInputRecord (KeyCode keyCode, ConsoleKey expectedConsoleKey)
  424. {
  425. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  426. {
  427. return;
  428. }
  429. // Arrange
  430. var key = new Key (keyCode);
  431. // Act
  432. WindowsConsole.InputRecord result = _converter.ToKeyInfo (key);
  433. // Assert
  434. Assert.Equal ((VK)expectedConsoleKey, result.KeyEvent.wVirtualKeyCode);
  435. }
  436. #endregion
  437. #region ToKeyInfo Tests - Modifiers
  438. [Theory]
  439. [InlineData (KeyCode.A | KeyCode.ShiftMask, WindowsConsole.ControlKeyState.ShiftPressed)]
  440. [InlineData (KeyCode.A | KeyCode.CtrlMask, WindowsConsole.ControlKeyState.LeftControlPressed)]
  441. [InlineData (KeyCode.A | KeyCode.AltMask, WindowsConsole.ControlKeyState.LeftAltPressed)]
  442. [InlineData (
  443. KeyCode.A | KeyCode.CtrlMask | KeyCode.AltMask,
  444. WindowsConsole.ControlKeyState.LeftControlPressed | WindowsConsole.ControlKeyState.LeftAltPressed)]
  445. [InlineData (
  446. KeyCode.A | KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask,
  447. WindowsConsole.ControlKeyState.ShiftPressed
  448. | WindowsConsole.ControlKeyState.LeftControlPressed
  449. | WindowsConsole.ControlKeyState.LeftAltPressed)]
  450. public void ToKeyInfo_WithModifiers_ReturnsExpectedControlKeyState (
  451. KeyCode keyCode,
  452. WindowsConsole.ControlKeyState expectedState
  453. )
  454. {
  455. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  456. {
  457. return;
  458. }
  459. // Arrange
  460. var key = new Key (keyCode);
  461. // Act
  462. WindowsConsole.InputRecord result = _converter.ToKeyInfo (key);
  463. // Assert
  464. Assert.Equal (expectedState, result.KeyEvent.dwControlKeyState);
  465. }
  466. #endregion
  467. #region ToKeyInfo Tests - Scan Codes
  468. [Theory]
  469. [InlineData (KeyCode.A, 30)]
  470. [InlineData (KeyCode.Enter, 28)]
  471. [InlineData (KeyCode.Esc, 1)]
  472. [InlineData (KeyCode.Space, 57)]
  473. [InlineData (KeyCode.F1, 59)]
  474. [InlineData (KeyCode.F10, 68)]
  475. [InlineData (KeyCode.CursorUp, 72)]
  476. [InlineData (KeyCode.Home, 71)]
  477. public void ToKeyInfo_ScanCodes_ReturnsExpectedScanCode (KeyCode keyCode, ushort expectedScanCode)
  478. {
  479. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  480. {
  481. return;
  482. }
  483. // Arrange
  484. var key = new Key (keyCode);
  485. // Act
  486. WindowsConsole.InputRecord result = _converter.ToKeyInfo (key);
  487. // Assert
  488. Assert.Equal (expectedScanCode, result.KeyEvent.wVirtualScanCode);
  489. }
  490. #endregion
  491. #region Round-Trip Tests
  492. [Theory]
  493. [InlineData (KeyCode.A)]
  494. [InlineData (KeyCode.A | KeyCode.ShiftMask)]
  495. [InlineData (KeyCode.A | KeyCode.CtrlMask)]
  496. [InlineData (KeyCode.Enter)]
  497. [InlineData (KeyCode.F1)]
  498. [InlineData (KeyCode.CursorUp)]
  499. [InlineData (KeyCode.Delete)]
  500. [InlineData (KeyCode.D5)]
  501. [InlineData (KeyCode.Space)]
  502. public void RoundTrip_ToKeyInfo_ToKey_PreservesKeyCode (KeyCode originalKeyCode)
  503. {
  504. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  505. {
  506. return;
  507. }
  508. // Arrange
  509. var originalKey = new Key (originalKeyCode);
  510. // Act
  511. WindowsConsole.InputRecord inputRecord = _converter.ToKeyInfo (originalKey);
  512. var roundTrippedKey = _converter.ToKey (inputRecord);
  513. // Assert
  514. Assert.Equal (originalKeyCode, roundTrippedKey.KeyCode);
  515. }
  516. [Theory]
  517. [InlineData ('a', ConsoleKey.A, false, false, false)]
  518. [InlineData ('A', ConsoleKey.A, true, false, false)]
  519. [InlineData ('a', ConsoleKey.A, false, false, true)] // Ctrl+A
  520. [InlineData ('0', ConsoleKey.D0, false, false, false)]
  521. public void RoundTrip_ToKey_ToKeyInfo_PreservesData (
  522. char unicodeChar,
  523. ConsoleKey consoleKey,
  524. bool shift,
  525. bool alt,
  526. bool ctrl
  527. )
  528. {
  529. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  530. {
  531. return;
  532. }
  533. // Arrange
  534. WindowsConsole.InputRecord originalRecord = CreateInputRecord (unicodeChar, consoleKey, shift, alt, ctrl);
  535. // Act
  536. var key = _converter.ToKey (originalRecord);
  537. WindowsConsole.InputRecord roundTrippedRecord = _converter.ToKeyInfo (key);
  538. // Assert
  539. Assert.Equal ((VK)consoleKey, roundTrippedRecord.KeyEvent.wVirtualKeyCode);
  540. // Check modifiers match
  541. var expectedState = WindowsConsole.ControlKeyState.NoControlKeyPressed;
  542. if (shift)
  543. {
  544. expectedState |= WindowsConsole.ControlKeyState.ShiftPressed;
  545. }
  546. if (alt)
  547. {
  548. expectedState |= WindowsConsole.ControlKeyState.LeftAltPressed;
  549. }
  550. if (ctrl)
  551. {
  552. expectedState |= WindowsConsole.ControlKeyState.LeftControlPressed;
  553. }
  554. Assert.True (roundTrippedRecord.KeyEvent.dwControlKeyState.HasFlag (expectedState));
  555. }
  556. #endregion
  557. #region CapsLock/NumLock Tests
  558. [Theory]
  559. [InlineData ('a', ConsoleKey.A, false, true)] // CapsLock on, no shift
  560. [InlineData ('A', ConsoleKey.A, true, true)] // CapsLock on, shift (should be lowercase from mapping)
  561. public void ToKey_WithCapsLock_ReturnsExpectedKeyCode (
  562. char unicodeChar,
  563. ConsoleKey consoleKey,
  564. bool shift,
  565. bool capsLock
  566. )
  567. {
  568. if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
  569. {
  570. return;
  571. }
  572. WindowsConsole.InputRecord inputRecord = CreateInputRecordWithLockStates (
  573. unicodeChar,
  574. consoleKey,
  575. shift,
  576. false,
  577. false,
  578. capsLock,
  579. false,
  580. false);
  581. // Act
  582. var result = _converter.ToKey (inputRecord);
  583. // Assert
  584. // The mapping should handle CapsLock properly via WindowsKeyHelper.MapKey
  585. Assert.NotEqual (KeyCode.Null, result.KeyCode);
  586. }
  587. #endregion
  588. #region Helper Methods
  589. private static WindowsConsole.InputRecord CreateInputRecord (
  590. char unicodeChar,
  591. ConsoleKey consoleKey,
  592. bool shift,
  593. bool alt,
  594. bool ctrl
  595. ) =>
  596. CreateInputRecordWithLockStates (unicodeChar, consoleKey, shift, alt, ctrl, false, false, false);
  597. private static WindowsConsole.InputRecord CreateInputRecordWithLockStates (
  598. char unicodeChar,
  599. ConsoleKey consoleKey,
  600. bool shift,
  601. bool alt,
  602. bool ctrl,
  603. bool capsLock,
  604. bool numLock,
  605. bool scrollLock
  606. )
  607. {
  608. var controlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed;
  609. if (shift)
  610. {
  611. controlKeyState |= WindowsConsole.ControlKeyState.ShiftPressed;
  612. }
  613. if (alt)
  614. {
  615. controlKeyState |= WindowsConsole.ControlKeyState.LeftAltPressed;
  616. }
  617. if (ctrl)
  618. {
  619. controlKeyState |= WindowsConsole.ControlKeyState.LeftControlPressed;
  620. }
  621. if (capsLock)
  622. {
  623. controlKeyState |= WindowsConsole.ControlKeyState.CapslockOn;
  624. }
  625. if (numLock)
  626. {
  627. controlKeyState |= WindowsConsole.ControlKeyState.NumlockOn;
  628. }
  629. if (scrollLock)
  630. {
  631. controlKeyState |= WindowsConsole.ControlKeyState.ScrolllockOn;
  632. }
  633. return new ()
  634. {
  635. EventType = WindowsConsole.EventType.Key,
  636. KeyEvent = new ()
  637. {
  638. bKeyDown = true,
  639. wRepeatCount = 1,
  640. wVirtualKeyCode = (VK)consoleKey,
  641. wVirtualScanCode = 0,
  642. UnicodeChar = unicodeChar,
  643. dwControlKeyState = controlKeyState
  644. }
  645. };
  646. }
  647. private static WindowsConsole.InputRecord CreateVKPacketInputRecord (char unicodeChar) =>
  648. new ()
  649. {
  650. EventType = WindowsConsole.EventType.Key,
  651. KeyEvent = new ()
  652. {
  653. bKeyDown = true,
  654. wRepeatCount = 1,
  655. wVirtualKeyCode = VK.PACKET,
  656. wVirtualScanCode = 0,
  657. UnicodeChar = unicodeChar,
  658. dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed
  659. }
  660. };
  661. #endregion
  662. }