mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
* Initial plan * Refactor Application.Mouse - Create IMouse interface and Mouse implementation Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add enhanced documentation for Application.Mouse property Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add parallelizable unit tests for IMouse interface Co-authored-by: tig <585482+tig@users.noreply.github.com> * Refactor Application.Mouse for decoupling and parallelism Co-authored-by: tig <585482+tig@users.noreply.github.com> * Move HandleMouseGrab method to IMouseGrabHandler interface Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add parallelizable tests for IMouse and IMouseGrabHandler interfaces Co-authored-by: tig <585482+tig@users.noreply.github.com> * Add MouseEventRoutingTests - 27 parallelizable tests for View mouse event handling Co-authored-by: tig <585482+tig@users.noreply.github.com> * Fix terminology: Replace parent/child with superView/subView in MouseEventRoutingTests Co-authored-by: tig <585482+tig@users.noreply.github.com> * Fix coding standards: Use explicit types and target-typed new() in test files Co-authored-by: tig <585482+tig@users.noreply.github.com> * Update coding standards documentation with explicit var and target-typed new() guidance Co-authored-by: tig <585482+tig@users.noreply.github.com> * Refactor Application classes and improve maintainability Refactored `Sixel` property to be immutable, enhancing thread safety. Cleaned up `ApplicationImpl` by removing redundant fields, restructuring methods (`CreateDriver`, `CreateSubcomponents`), and improving exception handling. Updated `Run<T>` and `Shutdown` methods for consistency. Standardized logging/debugging messages and fixed formatting issues. Reorganized `IApplication` interface, added detailed XML documentation, and grouped related methods logically. Performed general code cleanup, including fixing typos, improving readability, and removing legacy/unnecessary code to reduce technical debt. * Code cleanup * Remove unreferenced LayoutAndDraw method from ApplicationImpl * Code cleanup and TODOs - Updated namespaces to reflect the new structure. - Added `Driver`, `Force16Colors`, and `ForceDriver` properties. - Introduced `Sixel` collection for sixel image management. - Added lifecycle methods: `GetDriverTypes`, `Shutdown`, and events. - Refactored `Init` to support legacy and modern drivers. - Improved driver event handling and screen abstraction. - Updated `Run` method to align with the application lifecycle. - Simplified `IConsoleDriver` documentation. - Removed redundant methods and improved code readability. * Refactor LayoutAndDraw logic for better encapsulation Refactored `Application.Run` to delegate `LayoutAndDraw` to `ApplicationImpl.Instance.LayoutAndDraw`, improving separation of concerns. Renamed `forceDraw` to `forceRedraw` for clarity and moved `LayoutAndDraw` implementation to `ApplicationImpl`. Added a new `LayoutAndDraw` method in `ApplicationImpl` to handle layout and drawing, including managing `TopLevels`, handling active popovers, and refreshing the screen. Updated the `IApplication` interface to reflect the new method and improved its documentation. Implemented `RequestStop` in `ApplicationImpl` and fixed formatting inconsistencies in `Run<T>`. Added TODOs for future refactoring to encapsulate `Top` and `TopLevels` into an `IViewHierarchy` and move certain properties to `IApplication`. * Refactor ApplicationImpl to enhance mouse and keyboard support Added a new `Mouse` property to the `ApplicationImpl` class, replacing its previous declaration, to improve mouse functionality. Updated `MouseGrabHandler` to initialize with a default instance of `MouseGrabHandler`. Added comments to ensure the preservation of existing keyboard settings (`QuitKey`, `ArrangeKey`, `NextTabKey`) for backward compatibility. These changes enhance clarity, functionality, and maintainability of the class. * Merge IMouseGrabHandler into IMouse - consolidate mouse handling into single interface Co-authored-by: tig <585482+tig@users.noreply.github.com> * Rename Mouse to MouseImpl and Keyboard to KeyboardImpl for consistency Co-authored-by: tig <585482+tig@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: tig <585482+tig@users.noreply.github.com> Co-authored-by: Tig <tig@users.noreply.github.com>
478 lines
13 KiB
C#
478 lines
13 KiB
C#
#nullable enable
|
|
using Terminal.Gui.App;
|
|
|
|
namespace UnitTests_Parallelizable.ApplicationTests;
|
|
|
|
/// <summary>
|
|
/// Parallelizable tests for keyboard handling.
|
|
/// These tests use isolated instances of <see cref="IKeyboard"/> to avoid static state dependencies.
|
|
/// </summary>
|
|
public class KeyboardTests
|
|
{
|
|
[Fact]
|
|
public void Constructor_InitializesKeyBindings ()
|
|
{
|
|
// Arrange & Act
|
|
var keyboard = new KeyboardImpl ();
|
|
|
|
// Assert
|
|
Assert.NotNull (keyboard.KeyBindings);
|
|
// Verify that some default bindings exist
|
|
Assert.True (keyboard.KeyBindings.TryGet (keyboard.QuitKey, out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void QuitKey_DefaultValue_IsEsc ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
|
|
// Assert
|
|
Assert.Equal (Key.Esc, keyboard.QuitKey);
|
|
}
|
|
|
|
[Fact]
|
|
public void QuitKey_SetValue_UpdatesKeyBindings ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
Key newQuitKey = Key.Q.WithCtrl;
|
|
|
|
// Act
|
|
keyboard.QuitKey = newQuitKey;
|
|
|
|
// Assert
|
|
Assert.Equal (newQuitKey, keyboard.QuitKey);
|
|
Assert.True (keyboard.KeyBindings.TryGet (newQuitKey, out KeyBinding binding));
|
|
Assert.Contains (Command.Quit, binding.Commands);
|
|
}
|
|
|
|
[Fact]
|
|
public void ArrangeKey_DefaultValue_IsCtrlF5 ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
|
|
// Assert
|
|
Assert.Equal (Key.F5.WithCtrl, keyboard.ArrangeKey);
|
|
}
|
|
|
|
[Fact]
|
|
public void NextTabKey_DefaultValue_IsTab ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
|
|
// Assert
|
|
Assert.Equal (Key.Tab, keyboard.NextTabKey);
|
|
}
|
|
|
|
[Fact]
|
|
public void PrevTabKey_DefaultValue_IsShiftTab ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
|
|
// Assert
|
|
Assert.Equal (Key.Tab.WithShift, keyboard.PrevTabKey);
|
|
}
|
|
|
|
[Fact]
|
|
public void NextTabGroupKey_DefaultValue_IsF6 ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
|
|
// Assert
|
|
Assert.Equal (Key.F6, keyboard.NextTabGroupKey);
|
|
}
|
|
|
|
[Fact]
|
|
public void PrevTabGroupKey_DefaultValue_IsShiftF6 ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
|
|
// Assert
|
|
Assert.Equal (Key.F6.WithShift, keyboard.PrevTabGroupKey);
|
|
}
|
|
|
|
[Fact]
|
|
public void KeyBindings_Add_CanAddCustomBinding ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
Key customKey = Key.K.WithCtrl;
|
|
|
|
// Act
|
|
keyboard.KeyBindings.Add (customKey, Command.Accept);
|
|
|
|
// Assert
|
|
Assert.True (keyboard.KeyBindings.TryGet (customKey, out KeyBinding binding));
|
|
Assert.Contains (Command.Accept, binding.Commands);
|
|
}
|
|
|
|
[Fact]
|
|
public void KeyBindings_Remove_CanRemoveBinding ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
Key customKey = Key.K.WithCtrl;
|
|
keyboard.KeyBindings.Add (customKey, Command.Accept);
|
|
|
|
// Act
|
|
keyboard.KeyBindings.Remove (customKey);
|
|
|
|
// Assert
|
|
Assert.False (keyboard.KeyBindings.TryGet (customKey, out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void KeyDown_Event_CanBeSubscribed ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
bool eventRaised = false;
|
|
|
|
// Act
|
|
keyboard.KeyDown += (sender, key) =>
|
|
{
|
|
eventRaised = true;
|
|
};
|
|
|
|
// Assert - event subscription doesn't throw
|
|
Assert.False (eventRaised); // Event hasn't been raised yet
|
|
}
|
|
|
|
[Fact]
|
|
public void KeyUp_Event_CanBeSubscribed ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
bool eventRaised = false;
|
|
|
|
// Act
|
|
keyboard.KeyUp += (sender, key) =>
|
|
{
|
|
eventRaised = true;
|
|
};
|
|
|
|
// Assert - event subscription doesn't throw
|
|
Assert.False (eventRaised); // Event hasn't been raised yet
|
|
}
|
|
|
|
[Fact]
|
|
public void InvokeCommand_WithInvalidCommand_ThrowsNotSupportedException ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
// Pick a command that isn't registered
|
|
Command invalidCommand = (Command)9999;
|
|
Key testKey = Key.A;
|
|
var binding = new KeyBinding ([invalidCommand]);
|
|
|
|
// Act & Assert
|
|
Assert.Throws<NotSupportedException> (() => keyboard.InvokeCommand (invalidCommand, testKey, binding));
|
|
}
|
|
|
|
[Fact]
|
|
public void Multiple_Keyboards_CanExistIndependently ()
|
|
{
|
|
// Arrange & Act
|
|
var keyboard1 = new KeyboardImpl ();
|
|
var keyboard2 = new KeyboardImpl ();
|
|
|
|
keyboard1.QuitKey = Key.Q.WithCtrl;
|
|
keyboard2.QuitKey = Key.X.WithCtrl;
|
|
|
|
// Assert - each keyboard maintains independent state
|
|
Assert.Equal (Key.Q.WithCtrl, keyboard1.QuitKey);
|
|
Assert.Equal (Key.X.WithCtrl, keyboard2.QuitKey);
|
|
Assert.NotEqual (keyboard1.QuitKey, keyboard2.QuitKey);
|
|
}
|
|
|
|
[Fact]
|
|
public void KeyBindings_Replace_UpdatesExistingBinding ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
Key oldKey = Key.Esc;
|
|
Key newKey = Key.Q.WithCtrl;
|
|
|
|
// Verify initial state
|
|
Assert.True (keyboard.KeyBindings.TryGet (oldKey, out KeyBinding oldBinding));
|
|
Assert.Contains (Command.Quit, oldBinding.Commands);
|
|
|
|
// Act
|
|
keyboard.KeyBindings.Replace (oldKey, newKey);
|
|
|
|
// Assert - old key should no longer have the binding
|
|
Assert.False (keyboard.KeyBindings.TryGet (oldKey, out _));
|
|
// New key should have the binding
|
|
Assert.True (keyboard.KeyBindings.TryGet (newKey, out KeyBinding newBinding));
|
|
Assert.Contains (Command.Quit, newBinding.Commands);
|
|
}
|
|
|
|
[Fact]
|
|
public void KeyBindings_Clear_RemovesAllBindings ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
// Verify initial state has bindings
|
|
Assert.True (keyboard.KeyBindings.TryGet (keyboard.QuitKey, out _));
|
|
|
|
// Act
|
|
keyboard.KeyBindings.Clear ();
|
|
|
|
// Assert - previously existing binding is gone
|
|
Assert.False (keyboard.KeyBindings.TryGet (keyboard.QuitKey, out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void AddKeyBindings_PopulatesDefaultBindings ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
keyboard.KeyBindings.Clear ();
|
|
Assert.False (keyboard.KeyBindings.TryGet (keyboard.QuitKey, out _));
|
|
|
|
// Act
|
|
keyboard.AddKeyBindings ();
|
|
|
|
// Assert
|
|
Assert.True (keyboard.KeyBindings.TryGet (keyboard.QuitKey, out KeyBinding binding));
|
|
Assert.Contains (Command.Quit, binding.Commands);
|
|
}
|
|
|
|
// Migrated from UnitTests/Application/KeyboardTests.cs
|
|
|
|
[Fact]
|
|
public void KeyBindings_Add_Adds ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
|
|
// Act
|
|
keyboard.KeyBindings.Add (Key.A, Command.Accept);
|
|
keyboard.KeyBindings.Add (Key.B, Command.Accept);
|
|
|
|
// Assert
|
|
Assert.True (keyboard.KeyBindings.TryGet (Key.A, out KeyBinding binding));
|
|
Assert.Null (binding.Target);
|
|
Assert.True (keyboard.KeyBindings.TryGet (Key.B, out binding));
|
|
Assert.Null (binding.Target);
|
|
}
|
|
|
|
[Fact]
|
|
public void KeyBindings_Remove_Removes ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
keyboard.KeyBindings.Add (Key.A, Command.Accept);
|
|
Assert.True (keyboard.KeyBindings.TryGet (Key.A, out _));
|
|
|
|
// Act
|
|
keyboard.KeyBindings.Remove (Key.A);
|
|
|
|
// Assert
|
|
Assert.False (keyboard.KeyBindings.TryGet (Key.A, out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void QuitKey_Default_Is_Esc ()
|
|
{
|
|
// Arrange & Act
|
|
var keyboard = new KeyboardImpl ();
|
|
|
|
// Assert
|
|
Assert.Equal (Key.Esc, keyboard.QuitKey);
|
|
}
|
|
|
|
[Fact]
|
|
public void QuitKey_Setter_UpdatesBindings ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
Key prevKey = keyboard.QuitKey;
|
|
|
|
// Act - Change QuitKey
|
|
keyboard.QuitKey = Key.C.WithCtrl;
|
|
|
|
// Assert - Old key should no longer trigger quit
|
|
Assert.False (keyboard.KeyBindings.TryGet (prevKey, out _));
|
|
// New key should trigger quit
|
|
Assert.True (keyboard.KeyBindings.TryGet (Key.C.WithCtrl, out KeyBinding binding));
|
|
Assert.Contains (Command.Quit, binding.Commands);
|
|
}
|
|
|
|
[Fact]
|
|
public void NextTabKey_Setter_UpdatesBindings ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
Key prevKey = keyboard.NextTabKey;
|
|
Key newKey = Key.N.WithCtrl;
|
|
|
|
// Act
|
|
keyboard.NextTabKey = newKey;
|
|
|
|
// Assert
|
|
Assert.Equal (newKey, keyboard.NextTabKey);
|
|
Assert.True (keyboard.KeyBindings.TryGet (newKey, out KeyBinding binding));
|
|
Assert.Contains (Command.NextTabStop, binding.Commands);
|
|
}
|
|
|
|
[Fact]
|
|
public void PrevTabKey_Setter_UpdatesBindings ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
Key newKey = Key.P.WithCtrl;
|
|
|
|
// Act
|
|
keyboard.PrevTabKey = newKey;
|
|
|
|
// Assert
|
|
Assert.Equal (newKey, keyboard.PrevTabKey);
|
|
Assert.True (keyboard.KeyBindings.TryGet (newKey, out KeyBinding binding));
|
|
Assert.Contains (Command.PreviousTabStop, binding.Commands);
|
|
}
|
|
|
|
[Fact]
|
|
public void NextTabGroupKey_Setter_UpdatesBindings ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
Key newKey = Key.PageDown.WithCtrl;
|
|
|
|
// Act
|
|
keyboard.NextTabGroupKey = newKey;
|
|
|
|
// Assert
|
|
Assert.Equal (newKey, keyboard.NextTabGroupKey);
|
|
Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, keyboard.NextTabGroupKey.KeyCode);
|
|
Assert.True (keyboard.KeyBindings.TryGet (newKey, out KeyBinding binding));
|
|
Assert.Contains (Command.NextTabGroup, binding.Commands);
|
|
}
|
|
|
|
[Fact]
|
|
public void PrevTabGroupKey_Setter_UpdatesBindings ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
Key newKey = Key.PageUp.WithCtrl;
|
|
|
|
// Act
|
|
keyboard.PrevTabGroupKey = newKey;
|
|
|
|
// Assert
|
|
Assert.Equal (newKey, keyboard.PrevTabGroupKey);
|
|
Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, keyboard.PrevTabGroupKey.KeyCode);
|
|
Assert.True (keyboard.KeyBindings.TryGet (newKey, out KeyBinding binding));
|
|
Assert.Contains (Command.PreviousTabGroup, binding.Commands);
|
|
}
|
|
|
|
[Fact]
|
|
public void ArrangeKey_Setter_UpdatesBindings ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
Key newKey = Key.A.WithCtrl;
|
|
|
|
// Act
|
|
keyboard.ArrangeKey = newKey;
|
|
|
|
// Assert
|
|
Assert.Equal (newKey, keyboard.ArrangeKey);
|
|
Assert.True (keyboard.KeyBindings.TryGet (newKey, out KeyBinding binding));
|
|
Assert.Contains (Command.Arrange, binding.Commands);
|
|
}
|
|
|
|
[Fact]
|
|
public void KeyBindings_AddWithTarget_StoresTarget ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
var view = new View ();
|
|
|
|
// Act
|
|
keyboard.KeyBindings.Add (Key.A.WithCtrl, view, Command.Accept);
|
|
|
|
// Assert
|
|
Assert.True (keyboard.KeyBindings.TryGet (Key.A.WithCtrl, out KeyBinding binding));
|
|
Assert.Equal (view, binding.Target);
|
|
Assert.Contains (Command.Accept, binding.Commands);
|
|
|
|
view.Dispose ();
|
|
}
|
|
|
|
[Fact]
|
|
public void InvokeCommandsBoundToKey_ReturnsNull_WhenNoBindingExists ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
Key unboundKey = Key.Z.WithAlt.WithCtrl;
|
|
|
|
// Act
|
|
bool? result = keyboard.InvokeCommandsBoundToKey (unboundKey);
|
|
|
|
// Assert
|
|
Assert.Null (result);
|
|
}
|
|
|
|
[Fact]
|
|
public void InvokeCommandsBoundToKey_InvokesCommand_WhenBindingExists ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
// QuitKey has a bound command by default
|
|
|
|
// Act
|
|
bool? result = keyboard.InvokeCommandsBoundToKey (keyboard.QuitKey);
|
|
|
|
// Assert
|
|
// Command.Quit would normally call Application.RequestStop,
|
|
// but in isolation it should return true (handled)
|
|
Assert.NotNull (result);
|
|
}
|
|
|
|
[Fact]
|
|
public void Multiple_Keyboards_Independent_KeyBindings ()
|
|
{
|
|
// Arrange
|
|
var keyboard1 = new KeyboardImpl ();
|
|
var keyboard2 = new KeyboardImpl ();
|
|
|
|
// Act
|
|
keyboard1.KeyBindings.Add (Key.X, Command.Accept);
|
|
keyboard2.KeyBindings.Add (Key.Y, Command.Cancel);
|
|
|
|
// Assert
|
|
Assert.True (keyboard1.KeyBindings.TryGet (Key.X, out _));
|
|
Assert.False (keyboard1.KeyBindings.TryGet (Key.Y, out _));
|
|
|
|
Assert.True (keyboard2.KeyBindings.TryGet (Key.Y, out _));
|
|
Assert.False (keyboard2.KeyBindings.TryGet (Key.X, out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void KeyBindings_Replace_PreservesCommandsForNewKey ()
|
|
{
|
|
// Arrange
|
|
var keyboard = new KeyboardImpl ();
|
|
Key oldKey = Key.Esc;
|
|
Key newKey = Key.Q.WithCtrl;
|
|
|
|
// Get the commands from the old binding
|
|
Assert.True (keyboard.KeyBindings.TryGet (oldKey, out KeyBinding oldBinding));
|
|
Command[] oldCommands = oldBinding.Commands.ToArray ();
|
|
|
|
// Act
|
|
keyboard.KeyBindings.Replace (oldKey, newKey);
|
|
|
|
// Assert - new key should have the same commands
|
|
Assert.True (keyboard.KeyBindings.TryGet (newKey, out KeyBinding newBinding));
|
|
Assert.Equal (oldCommands, newBinding.Commands);
|
|
}
|
|
}
|