mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
* Add comprehensive unit tests for WindowsKeyConverter - Implement 118 parallelizable unit tests for WindowsKeyConverter - Cover ToKey and ToKeyInfo methods with full bidirectional testing - Test basic characters, modifiers, special keys, function keys - Test VK_PACKET Unicode/IME input - Test OEM keys, NumPad keys, and lock states - Include round-trip conversion tests - All tests passing successfully Fixes #4389 * Add test documenting VK_PACKET surrogate pair limitation VK_PACKET sends surrogate pairs (e.g., emoji) as two separate events. Current WindowsKeyConverter processes each independently without combining. Test documents this limitation for future fix at InputProcessor level. * Mark WindowsKeyConverterTests as Windows-only Tests now skip on non-Windows platforms using SkipException in constructor. This prevents CI failures on macOS and Linux where Windows Console API is not available. * Properly mark WindowsKeyConverter tests as Windows-only Created custom xUnit attributes SkipOnNonWindowsFact and SkipOnNonWindowsTheory that automatically skip tests on non-Windows platforms. This prevents CI failures on macOS and Linux. * Refactor and enhance test coverage for input processors Refactored `ApplicationImpl.cs` to simplify its structure by removing the `_stopAfterFirstIteration` field. Reintroduced and modernized test files with improved structure and coverage: - `NetInputProcessorTests.cs`: Added tests for `ConsoleKeyInfo` to `Rune`/`Key` conversion and queue processing. - `WindowSizeMonitorTests.cs`: Added tests for size change event handling. - `WindowsInputProcessorTests.cs`: Added tests for keyboard and mouse input processing, including mouse flag mapping. - `WindowsKeyConverterTests.cs`: Added comprehensive tests for `InputRecord` to `Key` conversion, covering OEM keys, modifiers, Unicode, and round-trip integrity. Improved test coverage for edge cases, introduced parameterized tests, and documented known limitations for future improvements. * Use Trait-based platform filtering for WindowsKeyConverter tests Added [Trait('Platform', 'Windows')] and [Collection('Global Test Setup')] attributes. Tests will run on Windows but can be filtered in CI on other platforms using --filter 'Platform!=Windows' if needed. This approach doesn't interfere with GlobalTestSetup and works correctly with xUnit. * Filter Windows-specific tests on non-Windows CI platforms Added --filter 'Platform!=Windows' for Linux and macOS runners to exclude WindowsKeyConverterTests which require Windows Console APIs. Windows runner runs all tests normally. * Fix log path typo and remove Codecov upload step Corrected a typo in the log directory path from `logs/UnitTestsParallelable/` to `logs/UnitTestsParallelizable/`. Removed the "Upload Parallelizable UnitTests Coverage to Codecov" step, which was conditional on `matrix.os == 'ubuntu-latest'` and used the `codecov/codecov-action@v4` action. This change improves log handling and removes the Codecov integration. * Refactor application reset logic Replaced `Application.ResetState(true)` with a more explicit reset mechanism. Introduced `ApplicationImpl.SetInstance(null)` to clear the application instance and added `CM.Disable(true)` to disable specific components. This change improves control over the reset process and ensures a more granular approach to application state management. * Improve null safety with ?. and ! operators Enhanced null safety across the codebase by introducing the null-conditional operator (`?.`) and null-forgiving operator (`!`) where appropriate. - Updated `app` and `driver` method calls to use `?.` to prevent potential `NullReferenceException` errors. - Added `!` to assert non-nullability in cases like `e.Value!.ToString()` and `app.Driver?.Contents!`. - Modified `lv.SelectedItemChanged` event handler to ensure safe handling of nullable values. - Updated `app.Shutdown()`, `app.LayoutAndDraw()`, and mouse event handling to use `?.`. - Ensured `driver.SetScreenSize` is invoked only when `driver` is not null. - Improved string concatenation logic with null-forgiving operator for `Contents`. - General improvements to null safety to make the code more robust and resilient to null references. * Improve Unicode tests and clarify surrogate pair handling Updated `WindowsKeyConverterTests` to enhance readability, improve test data, and clarify comments. Key changes include: - Reformatted `[Collection]` and `[Trait]` attributes for consistency. - Replaced placeholder Unicode characters with meaningful examples: - Chinese: `中`, Japanese: `日`, Korean: `한`, Accented: `é`, Euro: `€`, Greek: `Ω`. - Updated comments to replace placeholder emojis (`??`) with `😀` (U+1F600) for clarity. - Adjusted surrogate pair test data to accurately reflect `😀`. - Improved documentation of current limitations and future fixes for surrogate pair handling. These changes ensure more accurate and meaningful test cases while improving code clarity. * Ensure platform-specific test execution on Windows Added `System.Runtime.InteropServices` and `Xunit.Sdk` namespaces to `WindowsKeyConverterTests.cs` to support platform checks and test setup. Marked the test class with `[Collection("Global Test Setup")]` to enable shared test setup. Updated the `ToKey_NumPadKeys_ReturnsExpectedKeyCode` method to include a platform check, ensuring the test only runs on Windows platforms. * Integrate TrueColor support into ColorPicker Merged TrueColors functionality into ColorPicker, enhancing the scenario with TrueColor demonstration and gradient features. Updated `ColorPicker.cs` to include driver information, TrueColor support indicators, and a toggle for `Force16Colors`. Removed `TrueColors.cs` as its functionality is now consolidated. Refined `ColorBar` to use dynamic height with `Dim.Auto` for better flexibility. Added documentation to `HueBar` to clarify its role in representing the Hue component in HSL color space. * Revert workflow change. * Reverted attribute that didn't actualy work.
799 lines
26 KiB
C#
799 lines
26 KiB
C#
using System.Runtime.InteropServices;
|
|
|
|
namespace UnitTests_Parallelizable.DriverTests;
|
|
|
|
[Collection ("Global Test Setup")]
|
|
[Trait ("Platform", "Windows")]
|
|
public class WindowsKeyConverterTests
|
|
{
|
|
private readonly WindowsKeyConverter _converter = new ();
|
|
|
|
#region ToKey Tests - Basic Characters
|
|
|
|
[Theory]
|
|
[InlineData ('a', ConsoleKey.A, false, false, false, KeyCode.A)] // lowercase a
|
|
[InlineData ('A', ConsoleKey.A, true, false, false, KeyCode.A | KeyCode.ShiftMask)] // uppercase A
|
|
[InlineData ('z', ConsoleKey.Z, false, false, false, KeyCode.Z)]
|
|
[InlineData ('Z', ConsoleKey.Z, true, false, false, KeyCode.Z | KeyCode.ShiftMask)]
|
|
public void ToKey_LetterKeys_ReturnsExpectedKeyCode (
|
|
char unicodeChar,
|
|
ConsoleKey consoleKey,
|
|
bool shift,
|
|
bool alt,
|
|
bool ctrl,
|
|
KeyCode expectedKeyCode
|
|
)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
WindowsConsole.InputRecord inputRecord = CreateInputRecord (unicodeChar, consoleKey, shift, alt, ctrl);
|
|
|
|
// Act
|
|
var result = _converter.ToKey (inputRecord);
|
|
|
|
// Assert
|
|
Assert.Equal (expectedKeyCode, result.KeyCode);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData ('0', ConsoleKey.D0, false, false, false, KeyCode.D0)]
|
|
[InlineData ('1', ConsoleKey.D1, false, false, false, KeyCode.D1)]
|
|
[InlineData ('9', ConsoleKey.D9, false, false, false, KeyCode.D9)]
|
|
public void ToKey_NumberKeys_ReturnsExpectedKeyCode (
|
|
char unicodeChar,
|
|
ConsoleKey consoleKey,
|
|
bool shift,
|
|
bool alt,
|
|
bool ctrl,
|
|
KeyCode expectedKeyCode
|
|
)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
WindowsConsole.InputRecord inputRecord = CreateInputRecord (unicodeChar, consoleKey, shift, alt, ctrl);
|
|
|
|
// Act
|
|
var result = _converter.ToKey (inputRecord);
|
|
|
|
// Assert
|
|
Assert.Equal (expectedKeyCode, result.KeyCode);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ToKey Tests - Modifiers
|
|
|
|
[Theory]
|
|
[InlineData ('a', ConsoleKey.A, false, false, true, KeyCode.A | KeyCode.CtrlMask)] // Ctrl+A
|
|
[InlineData ('A', ConsoleKey.A, true, false, true, KeyCode.A | KeyCode.ShiftMask | KeyCode.CtrlMask)] // Ctrl+Shift+A (Windows keeps ShiftMask)
|
|
[InlineData ('a', ConsoleKey.A, false, true, false, KeyCode.A | KeyCode.AltMask)] // Alt+A
|
|
[InlineData ('A', ConsoleKey.A, true, true, false, KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask)] // Alt+Shift+A
|
|
[InlineData ('a', ConsoleKey.A, false, true, true, KeyCode.A | KeyCode.CtrlMask | KeyCode.AltMask)] // Ctrl+Alt+A
|
|
public void ToKey_WithModifiers_ReturnsExpectedKeyCode (
|
|
char unicodeChar,
|
|
ConsoleKey consoleKey,
|
|
bool shift,
|
|
bool alt,
|
|
bool ctrl,
|
|
KeyCode expectedKeyCode
|
|
)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
WindowsConsole.InputRecord inputRecord = CreateInputRecord (unicodeChar, consoleKey, shift, alt, ctrl);
|
|
|
|
// Act
|
|
var result = _converter.ToKey (inputRecord);
|
|
|
|
// Assert
|
|
Assert.Equal (expectedKeyCode, result.KeyCode);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ToKey Tests - Special Keys
|
|
|
|
[Theory]
|
|
[InlineData (ConsoleKey.Enter, KeyCode.Enter)]
|
|
[InlineData (ConsoleKey.Escape, KeyCode.Esc)]
|
|
[InlineData (ConsoleKey.Tab, KeyCode.Tab)]
|
|
[InlineData (ConsoleKey.Backspace, KeyCode.Backspace)]
|
|
[InlineData (ConsoleKey.Delete, KeyCode.Delete)]
|
|
[InlineData (ConsoleKey.Insert, KeyCode.Insert)]
|
|
[InlineData (ConsoleKey.Home, KeyCode.Home)]
|
|
[InlineData (ConsoleKey.End, KeyCode.End)]
|
|
[InlineData (ConsoleKey.PageUp, KeyCode.PageUp)]
|
|
[InlineData (ConsoleKey.PageDown, KeyCode.PageDown)]
|
|
[InlineData (ConsoleKey.UpArrow, KeyCode.CursorUp)]
|
|
[InlineData (ConsoleKey.DownArrow, KeyCode.CursorDown)]
|
|
[InlineData (ConsoleKey.LeftArrow, KeyCode.CursorLeft)]
|
|
[InlineData (ConsoleKey.RightArrow, KeyCode.CursorRight)]
|
|
public void ToKey_SpecialKeys_ReturnsExpectedKeyCode (ConsoleKey consoleKey, KeyCode expectedKeyCode)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
char unicodeChar = consoleKey switch
|
|
{
|
|
ConsoleKey.Enter => '\r',
|
|
ConsoleKey.Escape => '\u001B',
|
|
ConsoleKey.Tab => '\t',
|
|
ConsoleKey.Backspace => '\b',
|
|
_ => '\0'
|
|
};
|
|
WindowsConsole.InputRecord inputRecord = CreateInputRecord (unicodeChar, consoleKey, false, false, false);
|
|
|
|
// Act
|
|
var result = _converter.ToKey (inputRecord);
|
|
|
|
// Assert
|
|
Assert.Equal (expectedKeyCode, result.KeyCode);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData (ConsoleKey.F1, KeyCode.F1)]
|
|
[InlineData (ConsoleKey.F2, KeyCode.F2)]
|
|
[InlineData (ConsoleKey.F3, KeyCode.F3)]
|
|
[InlineData (ConsoleKey.F4, KeyCode.F4)]
|
|
[InlineData (ConsoleKey.F5, KeyCode.F5)]
|
|
[InlineData (ConsoleKey.F6, KeyCode.F6)]
|
|
[InlineData (ConsoleKey.F7, KeyCode.F7)]
|
|
[InlineData (ConsoleKey.F8, KeyCode.F8)]
|
|
[InlineData (ConsoleKey.F9, KeyCode.F9)]
|
|
[InlineData (ConsoleKey.F10, KeyCode.F10)]
|
|
[InlineData (ConsoleKey.F11, KeyCode.F11)]
|
|
[InlineData (ConsoleKey.F12, KeyCode.F12)]
|
|
public void ToKey_FunctionKeys_ReturnsExpectedKeyCode (ConsoleKey consoleKey, KeyCode expectedKeyCode)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
WindowsConsole.InputRecord inputRecord = CreateInputRecord ('\0', consoleKey, false, false, false);
|
|
|
|
// Act
|
|
var result = _converter.ToKey (inputRecord);
|
|
|
|
// Assert
|
|
Assert.Equal (expectedKeyCode, result.KeyCode);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ToKey Tests - VK_PACKET (Unicode/IME)
|
|
|
|
[Theory]
|
|
[InlineData ('中')] // Chinese character
|
|
[InlineData ('日')] // Japanese character
|
|
[InlineData ('한')] // Korean character
|
|
[InlineData ('é')] // Accented character
|
|
[InlineData ('€')] // Euro symbol
|
|
[InlineData ('Ω')] // Greek character
|
|
public void ToKey_VKPacket_Unicode_ReturnsExpectedCharacter (char unicodeChar)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
WindowsConsole.InputRecord inputRecord = CreateVKPacketInputRecord (unicodeChar);
|
|
|
|
// Act
|
|
var result = _converter.ToKey (inputRecord);
|
|
|
|
// Assert
|
|
Assert.Equal ((KeyCode)unicodeChar, result.KeyCode);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToKey_VKPacket_ZeroChar_ReturnsNull ()
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
WindowsConsole.InputRecord inputRecord = CreateVKPacketInputRecord ('\0');
|
|
|
|
// Act
|
|
var result = _converter.ToKey (inputRecord);
|
|
|
|
// Assert
|
|
Assert.Equal (KeyCode.Null, result.KeyCode);
|
|
}
|
|
|
|
[Fact]
|
|
public void ToKey_VKPacket_SurrogatePair_DocumentsCurrentLimitation ()
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Emoji '😀' (U+1F600) requires a surrogate pair: High=U+D83D, Low=U+DE00
|
|
// Windows sends this as TWO consecutive VK_PACKET events (one for each char)
|
|
// because KeyEventRecord.UnicodeChar is a single 16-bit char field.
|
|
//
|
|
// CURRENT LIMITATION: WindowsKeyConverter processes each event independently
|
|
// and does not combine surrogate pairs into a single Rune/KeyCode.
|
|
// This test documents the current (incorrect) behavior.
|
|
//
|
|
// TODO: Implement proper surrogate pair handling at the InputProcessor level
|
|
// to combine consecutive high+low surrogate events into a single Key with the
|
|
// complete Unicode codepoint.
|
|
// See: https://docs.microsoft.com/en-us/windows/console/key-event-record
|
|
|
|
var highSurrogate = '\uD83D'; // High surrogate for 😀
|
|
var lowSurrogate = '\uDE00'; // Low surrogate for 😀
|
|
|
|
// First event with high surrogate
|
|
WindowsConsole.InputRecord highRecord = CreateVKPacketInputRecord (highSurrogate);
|
|
var highResult = _converter.ToKey (highRecord);
|
|
|
|
// Second event with low surrogate
|
|
WindowsConsole.InputRecord lowRecord = CreateVKPacketInputRecord (lowSurrogate);
|
|
var lowResult = _converter.ToKey (lowRecord);
|
|
|
|
// Currently each surrogate half is processed independently as invalid KeyCodes
|
|
// These assertions document the current (broken) behavior
|
|
Assert.Equal ((KeyCode)highSurrogate, highResult.KeyCode);
|
|
Assert.Equal ((KeyCode)lowSurrogate, lowResult.KeyCode);
|
|
|
|
// What SHOULD happen (future fix):
|
|
// The InputProcessor should detect the surrogate pair and combine them:
|
|
// var expectedRune = new Rune(0x1F600); // 😀
|
|
// Assert.Equal((KeyCode)expectedRune.Value, combinedResult.KeyCode);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ToKey Tests - OEM Keys
|
|
|
|
[Theory]
|
|
[InlineData (';', ConsoleKey.Oem1, false, (KeyCode)';')]
|
|
[InlineData (':', ConsoleKey.Oem1, true, (KeyCode)':')]
|
|
[InlineData ('/', ConsoleKey.Oem2, false, (KeyCode)'/')]
|
|
[InlineData ('?', ConsoleKey.Oem2, true, (KeyCode)'?')]
|
|
[InlineData (',', ConsoleKey.OemComma, false, (KeyCode)',')]
|
|
[InlineData ('<', ConsoleKey.OemComma, true, (KeyCode)'<')]
|
|
[InlineData ('.', ConsoleKey.OemPeriod, false, (KeyCode)'.')]
|
|
[InlineData ('>', ConsoleKey.OemPeriod, true, (KeyCode)'>')]
|
|
[InlineData ('=', ConsoleKey.OemPlus, false, (KeyCode)'=')] // Un-shifted OemPlus is '='
|
|
[InlineData ('+', ConsoleKey.OemPlus, true, (KeyCode)'+')] // Shifted OemPlus is '+'
|
|
[InlineData ('-', ConsoleKey.OemMinus, false, (KeyCode)'-')]
|
|
[InlineData ('_', ConsoleKey.OemMinus, true, (KeyCode)'_')] // Shifted OemMinus is '_'
|
|
public void ToKey_OEMKeys_ReturnsExpectedKeyCode (
|
|
char unicodeChar,
|
|
ConsoleKey consoleKey,
|
|
bool shift,
|
|
KeyCode expectedKeyCode
|
|
)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
WindowsConsole.InputRecord inputRecord = CreateInputRecord (unicodeChar, consoleKey, shift, false, false);
|
|
|
|
// Act
|
|
var result = _converter.ToKey (inputRecord);
|
|
|
|
// Assert
|
|
Assert.Equal (expectedKeyCode, result.KeyCode);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ToKey Tests - NumPad
|
|
|
|
[Theory]
|
|
[InlineData ('0', ConsoleKey.NumPad0, KeyCode.D0)]
|
|
[InlineData ('1', ConsoleKey.NumPad1, KeyCode.D1)]
|
|
[InlineData ('5', ConsoleKey.NumPad5, KeyCode.D5)]
|
|
[InlineData ('9', ConsoleKey.NumPad9, KeyCode.D9)]
|
|
public void ToKey_NumPadKeys_ReturnsExpectedKeyCode (
|
|
char unicodeChar,
|
|
ConsoleKey consoleKey,
|
|
KeyCode expectedKeyCode
|
|
)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
WindowsConsole.InputRecord inputRecord = CreateInputRecord (unicodeChar, consoleKey, false, false, false);
|
|
|
|
// Act
|
|
var result = _converter.ToKey (inputRecord);
|
|
|
|
// Assert
|
|
Assert.Equal (expectedKeyCode, result.KeyCode);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData ('*', ConsoleKey.Multiply, (KeyCode)'*')]
|
|
[InlineData ('+', ConsoleKey.Add, (KeyCode)'+')]
|
|
[InlineData ('-', ConsoleKey.Subtract, (KeyCode)'-')]
|
|
[InlineData ('.', ConsoleKey.Decimal, (KeyCode)'.')]
|
|
[InlineData ('/', ConsoleKey.Divide, (KeyCode)'/')]
|
|
public void ToKey_NumPadOperators_ReturnsExpectedKeyCode (
|
|
char unicodeChar,
|
|
ConsoleKey consoleKey,
|
|
KeyCode expectedKeyCode
|
|
)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
WindowsConsole.InputRecord inputRecord = CreateInputRecord (unicodeChar, consoleKey, false, false, false);
|
|
|
|
// Act
|
|
var result = _converter.ToKey (inputRecord);
|
|
|
|
// Assert
|
|
Assert.Equal (expectedKeyCode, result.KeyCode);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ToKey Tests - Null/Empty
|
|
|
|
[Fact]
|
|
public void ToKey_NullKey_ReturnsEmpty ()
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
WindowsConsole.InputRecord inputRecord = CreateInputRecord ('\0', ConsoleKey.None, false, false, false);
|
|
|
|
// Act
|
|
var result = _converter.ToKey (inputRecord);
|
|
|
|
// Assert
|
|
Assert.Equal (Key.Empty, result);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ToKeyInfo Tests - Basic Keys
|
|
|
|
[Theory]
|
|
[InlineData (KeyCode.A, ConsoleKey.A, 'a')]
|
|
[InlineData (KeyCode.A | KeyCode.ShiftMask, ConsoleKey.A, 'A')]
|
|
[InlineData (KeyCode.Z, ConsoleKey.Z, 'z')]
|
|
[InlineData (KeyCode.Z | KeyCode.ShiftMask, ConsoleKey.Z, 'Z')]
|
|
public void ToKeyInfo_LetterKeys_ReturnsExpectedInputRecord (
|
|
KeyCode keyCode,
|
|
ConsoleKey expectedConsoleKey,
|
|
char expectedChar
|
|
)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
var key = new Key (keyCode);
|
|
|
|
// Act
|
|
WindowsConsole.InputRecord result = _converter.ToKeyInfo (key);
|
|
|
|
// Assert
|
|
Assert.Equal (WindowsConsole.EventType.Key, result.EventType);
|
|
Assert.Equal ((VK)expectedConsoleKey, result.KeyEvent.wVirtualKeyCode);
|
|
Assert.Equal (expectedChar, result.KeyEvent.UnicodeChar);
|
|
Assert.True (result.KeyEvent.bKeyDown);
|
|
Assert.Equal ((ushort)1, result.KeyEvent.wRepeatCount);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData (KeyCode.D0, ConsoleKey.D0, '0')]
|
|
[InlineData (KeyCode.D1, ConsoleKey.D1, '1')]
|
|
[InlineData (KeyCode.D9, ConsoleKey.D9, '9')]
|
|
public void ToKeyInfo_NumberKeys_ReturnsExpectedInputRecord (
|
|
KeyCode keyCode,
|
|
ConsoleKey expectedConsoleKey,
|
|
char expectedChar
|
|
)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
var key = new Key (keyCode);
|
|
|
|
// Act
|
|
WindowsConsole.InputRecord result = _converter.ToKeyInfo (key);
|
|
|
|
// Assert
|
|
Assert.Equal ((VK)expectedConsoleKey, result.KeyEvent.wVirtualKeyCode);
|
|
Assert.Equal (expectedChar, result.KeyEvent.UnicodeChar);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ToKeyInfo Tests - Special Keys
|
|
|
|
[Theory]
|
|
[InlineData (KeyCode.Enter, ConsoleKey.Enter, '\r')]
|
|
[InlineData (KeyCode.Esc, ConsoleKey.Escape, '\u001B')]
|
|
[InlineData (KeyCode.Tab, ConsoleKey.Tab, '\t')]
|
|
[InlineData (KeyCode.Backspace, ConsoleKey.Backspace, '\b')]
|
|
[InlineData (KeyCode.Space, ConsoleKey.Spacebar, ' ')]
|
|
public void ToKeyInfo_SpecialKeys_ReturnsExpectedInputRecord (
|
|
KeyCode keyCode,
|
|
ConsoleKey expectedConsoleKey,
|
|
char expectedChar
|
|
)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
var key = new Key (keyCode);
|
|
|
|
// Act
|
|
WindowsConsole.InputRecord result = _converter.ToKeyInfo (key);
|
|
|
|
// Assert
|
|
Assert.Equal ((VK)expectedConsoleKey, result.KeyEvent.wVirtualKeyCode);
|
|
Assert.Equal (expectedChar, result.KeyEvent.UnicodeChar);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData (KeyCode.Delete, ConsoleKey.Delete)]
|
|
[InlineData (KeyCode.Insert, ConsoleKey.Insert)]
|
|
[InlineData (KeyCode.Home, ConsoleKey.Home)]
|
|
[InlineData (KeyCode.End, ConsoleKey.End)]
|
|
[InlineData (KeyCode.PageUp, ConsoleKey.PageUp)]
|
|
[InlineData (KeyCode.PageDown, ConsoleKey.PageDown)]
|
|
[InlineData (KeyCode.CursorUp, ConsoleKey.UpArrow)]
|
|
[InlineData (KeyCode.CursorDown, ConsoleKey.DownArrow)]
|
|
[InlineData (KeyCode.CursorLeft, ConsoleKey.LeftArrow)]
|
|
[InlineData (KeyCode.CursorRight, ConsoleKey.RightArrow)]
|
|
public void ToKeyInfo_NavigationKeys_ReturnsExpectedInputRecord (KeyCode keyCode, ConsoleKey expectedConsoleKey)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
var key = new Key (keyCode);
|
|
|
|
// Act
|
|
WindowsConsole.InputRecord result = _converter.ToKeyInfo (key);
|
|
|
|
// Assert
|
|
Assert.Equal ((VK)expectedConsoleKey, result.KeyEvent.wVirtualKeyCode);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData (KeyCode.F1, ConsoleKey.F1)]
|
|
[InlineData (KeyCode.F5, ConsoleKey.F5)]
|
|
[InlineData (KeyCode.F10, ConsoleKey.F10)]
|
|
[InlineData (KeyCode.F12, ConsoleKey.F12)]
|
|
public void ToKeyInfo_FunctionKeys_ReturnsExpectedInputRecord (KeyCode keyCode, ConsoleKey expectedConsoleKey)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
var key = new Key (keyCode);
|
|
|
|
// Act
|
|
WindowsConsole.InputRecord result = _converter.ToKeyInfo (key);
|
|
|
|
// Assert
|
|
Assert.Equal ((VK)expectedConsoleKey, result.KeyEvent.wVirtualKeyCode);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ToKeyInfo Tests - Modifiers
|
|
|
|
[Theory]
|
|
[InlineData (KeyCode.A | KeyCode.ShiftMask, WindowsConsole.ControlKeyState.ShiftPressed)]
|
|
[InlineData (KeyCode.A | KeyCode.CtrlMask, WindowsConsole.ControlKeyState.LeftControlPressed)]
|
|
[InlineData (KeyCode.A | KeyCode.AltMask, WindowsConsole.ControlKeyState.LeftAltPressed)]
|
|
[InlineData (
|
|
KeyCode.A | KeyCode.CtrlMask | KeyCode.AltMask,
|
|
WindowsConsole.ControlKeyState.LeftControlPressed | WindowsConsole.ControlKeyState.LeftAltPressed)]
|
|
[InlineData (
|
|
KeyCode.A | KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask,
|
|
WindowsConsole.ControlKeyState.ShiftPressed
|
|
| WindowsConsole.ControlKeyState.LeftControlPressed
|
|
| WindowsConsole.ControlKeyState.LeftAltPressed)]
|
|
public void ToKeyInfo_WithModifiers_ReturnsExpectedControlKeyState (
|
|
KeyCode keyCode,
|
|
WindowsConsole.ControlKeyState expectedState
|
|
)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
var key = new Key (keyCode);
|
|
|
|
// Act
|
|
WindowsConsole.InputRecord result = _converter.ToKeyInfo (key);
|
|
|
|
// Assert
|
|
Assert.Equal (expectedState, result.KeyEvent.dwControlKeyState);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ToKeyInfo Tests - Scan Codes
|
|
|
|
[Theory]
|
|
[InlineData (KeyCode.A, 30)]
|
|
[InlineData (KeyCode.Enter, 28)]
|
|
[InlineData (KeyCode.Esc, 1)]
|
|
[InlineData (KeyCode.Space, 57)]
|
|
[InlineData (KeyCode.F1, 59)]
|
|
[InlineData (KeyCode.F10, 68)]
|
|
[InlineData (KeyCode.CursorUp, 72)]
|
|
[InlineData (KeyCode.Home, 71)]
|
|
public void ToKeyInfo_ScanCodes_ReturnsExpectedScanCode (KeyCode keyCode, ushort expectedScanCode)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
var key = new Key (keyCode);
|
|
|
|
// Act
|
|
WindowsConsole.InputRecord result = _converter.ToKeyInfo (key);
|
|
|
|
// Assert
|
|
Assert.Equal (expectedScanCode, result.KeyEvent.wVirtualScanCode);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Round-Trip Tests
|
|
|
|
[Theory]
|
|
[InlineData (KeyCode.A)]
|
|
[InlineData (KeyCode.A | KeyCode.ShiftMask)]
|
|
[InlineData (KeyCode.A | KeyCode.CtrlMask)]
|
|
[InlineData (KeyCode.Enter)]
|
|
[InlineData (KeyCode.F1)]
|
|
[InlineData (KeyCode.CursorUp)]
|
|
[InlineData (KeyCode.Delete)]
|
|
[InlineData (KeyCode.D5)]
|
|
[InlineData (KeyCode.Space)]
|
|
public void RoundTrip_ToKeyInfo_ToKey_PreservesKeyCode (KeyCode originalKeyCode)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
var originalKey = new Key (originalKeyCode);
|
|
|
|
// Act
|
|
WindowsConsole.InputRecord inputRecord = _converter.ToKeyInfo (originalKey);
|
|
var roundTrippedKey = _converter.ToKey (inputRecord);
|
|
|
|
// Assert
|
|
Assert.Equal (originalKeyCode, roundTrippedKey.KeyCode);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData ('a', ConsoleKey.A, false, false, false)]
|
|
[InlineData ('A', ConsoleKey.A, true, false, false)]
|
|
[InlineData ('a', ConsoleKey.A, false, false, true)] // Ctrl+A
|
|
[InlineData ('0', ConsoleKey.D0, false, false, false)]
|
|
public void RoundTrip_ToKey_ToKeyInfo_PreservesData (
|
|
char unicodeChar,
|
|
ConsoleKey consoleKey,
|
|
bool shift,
|
|
bool alt,
|
|
bool ctrl
|
|
)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Arrange
|
|
WindowsConsole.InputRecord originalRecord = CreateInputRecord (unicodeChar, consoleKey, shift, alt, ctrl);
|
|
|
|
// Act
|
|
var key = _converter.ToKey (originalRecord);
|
|
WindowsConsole.InputRecord roundTrippedRecord = _converter.ToKeyInfo (key);
|
|
|
|
// Assert
|
|
Assert.Equal ((VK)consoleKey, roundTrippedRecord.KeyEvent.wVirtualKeyCode);
|
|
|
|
// Check modifiers match
|
|
var expectedState = WindowsConsole.ControlKeyState.NoControlKeyPressed;
|
|
|
|
if (shift)
|
|
{
|
|
expectedState |= WindowsConsole.ControlKeyState.ShiftPressed;
|
|
}
|
|
|
|
if (alt)
|
|
{
|
|
expectedState |= WindowsConsole.ControlKeyState.LeftAltPressed;
|
|
}
|
|
|
|
if (ctrl)
|
|
{
|
|
expectedState |= WindowsConsole.ControlKeyState.LeftControlPressed;
|
|
}
|
|
|
|
Assert.True (roundTrippedRecord.KeyEvent.dwControlKeyState.HasFlag (expectedState));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region CapsLock/NumLock Tests
|
|
|
|
[Theory]
|
|
[InlineData ('a', ConsoleKey.A, false, true)] // CapsLock on, no shift
|
|
[InlineData ('A', ConsoleKey.A, true, true)] // CapsLock on, shift (should be lowercase from mapping)
|
|
public void ToKey_WithCapsLock_ReturnsExpectedKeyCode (
|
|
char unicodeChar,
|
|
ConsoleKey consoleKey,
|
|
bool shift,
|
|
bool capsLock
|
|
)
|
|
{
|
|
if (!RuntimeInformation.IsOSPlatform (OSPlatform.Windows))
|
|
{
|
|
return;
|
|
}
|
|
|
|
WindowsConsole.InputRecord inputRecord = CreateInputRecordWithLockStates (
|
|
unicodeChar,
|
|
consoleKey,
|
|
shift,
|
|
false,
|
|
false,
|
|
capsLock,
|
|
false,
|
|
false);
|
|
|
|
// Act
|
|
var result = _converter.ToKey (inputRecord);
|
|
|
|
// Assert
|
|
// The mapping should handle CapsLock properly via WindowsKeyHelper.MapKey
|
|
Assert.NotEqual (KeyCode.Null, result.KeyCode);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper Methods
|
|
|
|
private static WindowsConsole.InputRecord CreateInputRecord (
|
|
char unicodeChar,
|
|
ConsoleKey consoleKey,
|
|
bool shift,
|
|
bool alt,
|
|
bool ctrl
|
|
) =>
|
|
CreateInputRecordWithLockStates (unicodeChar, consoleKey, shift, alt, ctrl, false, false, false);
|
|
|
|
private static WindowsConsole.InputRecord CreateInputRecordWithLockStates (
|
|
char unicodeChar,
|
|
ConsoleKey consoleKey,
|
|
bool shift,
|
|
bool alt,
|
|
bool ctrl,
|
|
bool capsLock,
|
|
bool numLock,
|
|
bool scrollLock
|
|
)
|
|
{
|
|
var controlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed;
|
|
|
|
if (shift)
|
|
{
|
|
controlKeyState |= WindowsConsole.ControlKeyState.ShiftPressed;
|
|
}
|
|
|
|
if (alt)
|
|
{
|
|
controlKeyState |= WindowsConsole.ControlKeyState.LeftAltPressed;
|
|
}
|
|
|
|
if (ctrl)
|
|
{
|
|
controlKeyState |= WindowsConsole.ControlKeyState.LeftControlPressed;
|
|
}
|
|
|
|
if (capsLock)
|
|
{
|
|
controlKeyState |= WindowsConsole.ControlKeyState.CapslockOn;
|
|
}
|
|
|
|
if (numLock)
|
|
{
|
|
controlKeyState |= WindowsConsole.ControlKeyState.NumlockOn;
|
|
}
|
|
|
|
if (scrollLock)
|
|
{
|
|
controlKeyState |= WindowsConsole.ControlKeyState.ScrolllockOn;
|
|
}
|
|
|
|
return new ()
|
|
{
|
|
EventType = WindowsConsole.EventType.Key,
|
|
KeyEvent = new ()
|
|
{
|
|
bKeyDown = true,
|
|
wRepeatCount = 1,
|
|
wVirtualKeyCode = (VK)consoleKey,
|
|
wVirtualScanCode = 0,
|
|
UnicodeChar = unicodeChar,
|
|
dwControlKeyState = controlKeyState
|
|
}
|
|
};
|
|
}
|
|
|
|
private static WindowsConsole.InputRecord CreateVKPacketInputRecord (char unicodeChar) =>
|
|
new ()
|
|
{
|
|
EventType = WindowsConsole.EventType.Key,
|
|
KeyEvent = new ()
|
|
{
|
|
bKeyDown = true,
|
|
wRepeatCount = 1,
|
|
wVirtualKeyCode = VK.PACKET,
|
|
wVirtualScanCode = 0,
|
|
UnicodeChar = unicodeChar,
|
|
dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed
|
|
}
|
|
};
|
|
|
|
#endregion
|
|
}
|