mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-30 09:47:58 +01:00
This commit is contained in:
365
Tests/UnitTestsParallelizable/Input/EnqueueKeyEventTests.cs
Normal file
365
Tests/UnitTestsParallelizable/Input/EnqueueKeyEventTests.cs
Normal file
@@ -0,0 +1,365 @@
|
||||
#nullable enable
|
||||
using System.Collections.Concurrent;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
/// <summary>
|
||||
/// Parallelizable unit tests for IInput.EnqueueKeyDownEvent and InputProcessor.EnqueueKeyDownEvent.
|
||||
/// Tests validate the entire pipeline: Key → TInputRecord → Queue → ProcessQueue → Events.
|
||||
/// </summary>
|
||||
[Trait ("Category", "Input")]
|
||||
public class EnqueueKeyEventTests (ITestOutputHelper output)
|
||||
{
|
||||
private readonly ITestOutputHelper _output = output;
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Simulates the input thread by manually draining FakeInput's internal queue
|
||||
/// and moving items to the InputBuffer. This is needed because tests don't
|
||||
/// start the actual input thread via Run().
|
||||
/// </summary>
|
||||
private static void SimulateInputThread (FakeInput fakeInput, ConcurrentQueue<ConsoleKeyInfo> inputBuffer)
|
||||
{
|
||||
// FakeInput's Peek() checks _testInput
|
||||
while (fakeInput.Peek ())
|
||||
{
|
||||
// Read() drains _testInput and returns items
|
||||
foreach (ConsoleKeyInfo item in fakeInput.Read ())
|
||||
{
|
||||
// Manually add to InputBuffer (simulating what Run() would do)
|
||||
inputBuffer.Enqueue (item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the input queue with support for keys that may be held by the ANSI parser (like Esc).
|
||||
/// The parser holds Esc for 50ms waiting to see if it's part of an escape sequence.
|
||||
/// </summary>
|
||||
private static void ProcessQueueWithEscapeHandling (FakeInputProcessor processor, int maxAttempts = 3)
|
||||
{
|
||||
// First attempt - process immediately
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// For escape sequences, we may need to wait and process again
|
||||
// The parser holds escape for 50ms before releasing
|
||||
for (var attempt = 1; attempt < maxAttempts; attempt++)
|
||||
{
|
||||
Thread.Sleep (60); // Wait longer than the 50ms escape timeout
|
||||
processor.ProcessQueue (); // This should release any held escape keys
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FakeInput EnqueueKeyDownEvent Tests
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueKeyDownEvent_AddsSingleKeyToQueue ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
List<Key> receivedKeys = [];
|
||||
processor.KeyDown += (_, k) => receivedKeys.Add (k);
|
||||
|
||||
Key key = Key.A;
|
||||
|
||||
// Act
|
||||
processor.EnqueueKeyDownEvent (key);
|
||||
|
||||
// Simulate the input thread moving items from _testInput to InputBuffer
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert - Verify the key made it through
|
||||
Assert.Single (receivedKeys);
|
||||
Assert.Equal (key, receivedKeys [0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueKeyDownEvent_SupportsMultipleKeys ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
Key [] keys = [Key.A, Key.B, Key.C, Key.Enter];
|
||||
List<Key> receivedKeys = [];
|
||||
processor.KeyDown += (_, k) => receivedKeys.Add (k);
|
||||
|
||||
// Act
|
||||
foreach (Key key in keys)
|
||||
{
|
||||
processor.EnqueueKeyDownEvent (key);
|
||||
}
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.Equal (keys.Length, receivedKeys.Count);
|
||||
Assert.Equal (keys, receivedKeys);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (KeyCode.A, false, false, false)]
|
||||
[InlineData (KeyCode.A, true, false, false)] // Shift+A
|
||||
[InlineData (KeyCode.A, false, true, false)] // Ctrl+A
|
||||
[InlineData (KeyCode.A, false, false, true)] // Alt+A
|
||||
[InlineData (KeyCode.A, true, true, true)] // Ctrl+Shift+Alt+A
|
||||
public void FakeInput_EnqueueKeyDownEvent_PreservesModifiers (KeyCode keyCode, bool shift, bool ctrl, bool alt)
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
var key = new Key (keyCode);
|
||||
|
||||
if (shift)
|
||||
{
|
||||
key = key.WithShift;
|
||||
}
|
||||
|
||||
if (ctrl)
|
||||
{
|
||||
key = key.WithCtrl;
|
||||
}
|
||||
|
||||
if (alt)
|
||||
{
|
||||
key = key.WithAlt;
|
||||
}
|
||||
|
||||
Key? receivedKey = null;
|
||||
processor.KeyDown += (_, k) => receivedKey = k;
|
||||
|
||||
// Act
|
||||
processor.EnqueueKeyDownEvent (key);
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull (receivedKey);
|
||||
Assert.Equal (key.IsShift, receivedKey.IsShift);
|
||||
Assert.Equal (key.IsCtrl, receivedKey.IsCtrl);
|
||||
Assert.Equal (key.IsAlt, receivedKey.IsAlt);
|
||||
Assert.Equal (key.KeyCode, receivedKey.KeyCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (KeyCode.Enter)]
|
||||
[InlineData (KeyCode.Tab)]
|
||||
[InlineData (KeyCode.Esc)]
|
||||
[InlineData (KeyCode.Backspace)]
|
||||
[InlineData (KeyCode.Delete)]
|
||||
[InlineData (KeyCode.CursorUp)]
|
||||
[InlineData (KeyCode.CursorDown)]
|
||||
[InlineData (KeyCode.CursorLeft)]
|
||||
[InlineData (KeyCode.CursorRight)]
|
||||
[InlineData (KeyCode.F1)]
|
||||
[InlineData (KeyCode.F12)]
|
||||
public void FakeInput_EnqueueKeyDownEvent_SupportsSpecialKeys (KeyCode keyCode)
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
var key = new Key (keyCode);
|
||||
Key? receivedKey = null;
|
||||
processor.KeyDown += (_, k) => receivedKey = k;
|
||||
|
||||
// Act
|
||||
processor.EnqueueKeyDownEvent (key);
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
|
||||
// Esc is special - the ANSI parser holds it waiting for potential escape sequences
|
||||
// We need to process with delay to let the parser release it after timeout
|
||||
if (keyCode == KeyCode.Esc)
|
||||
{
|
||||
ProcessQueueWithEscapeHandling (processor);
|
||||
}
|
||||
else
|
||||
{
|
||||
processor.ProcessQueue ();
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.NotNull (receivedKey);
|
||||
Assert.Equal (key.KeyCode, receivedKey.KeyCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueKeyDownEvent_RaisesKeyDownAndKeyUpEvents ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
var keyDownCount = 0;
|
||||
var keyUpCount = 0;
|
||||
processor.KeyDown += (_, _) => keyDownCount++;
|
||||
processor.KeyUp += (_, _) => keyUpCount++;
|
||||
|
||||
// Act
|
||||
processor.EnqueueKeyDownEvent (Key.A);
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert - FakeDriver simulates KeyUp immediately after KeyDown
|
||||
Assert.Equal (1, keyDownCount);
|
||||
Assert.Equal (1, keyUpCount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region InputProcessor Pipeline Tests
|
||||
|
||||
[Fact]
|
||||
public void InputProcessor_EnqueueKeyDownEvent_RequiresTestableInput ()
|
||||
{
|
||||
// Arrange
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
|
||||
// Don't set InputImpl (or set to non-testable)
|
||||
|
||||
// Act & Assert - Should not throw, but also won't add to queue
|
||||
// (because InputImpl is null or not ITestableInput)
|
||||
processor.EnqueueKeyDownEvent (Key.A);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// No events should be raised since no input was added
|
||||
var eventRaised = false;
|
||||
processor.KeyDown += (_, _) => eventRaised = true;
|
||||
processor.ProcessQueue ();
|
||||
Assert.False (eventRaised);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InputProcessor_ProcessQueue_DrainsPendingInputRecords ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
List<Key> receivedKeys = [];
|
||||
processor.KeyDown += (_, k) => receivedKeys.Add (k);
|
||||
|
||||
// Act - Enqueue multiple keys before processing
|
||||
processor.EnqueueKeyDownEvent (Key.A);
|
||||
processor.EnqueueKeyDownEvent (Key.B);
|
||||
processor.EnqueueKeyDownEvent (Key.C);
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert - After processing, queue should be empty and all keys received
|
||||
Assert.Empty (queue);
|
||||
Assert.Equal (3, receivedKeys.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Thread Safety Tests
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueKeyDownEvent_IsThreadSafe ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
ConcurrentBag<Key> receivedKeys = [];
|
||||
processor.KeyDown += (_, k) => receivedKeys.Add (k);
|
||||
|
||||
const int threadCount = 10;
|
||||
const int keysPerThread = 100;
|
||||
Thread [] threads = new Thread [threadCount];
|
||||
|
||||
// Act - Enqueue keys from multiple threads
|
||||
for (var t = 0; t < threadCount; t++)
|
||||
{
|
||||
threads [t] = new (() =>
|
||||
{
|
||||
for (var i = 0; i < keysPerThread; i++)
|
||||
{
|
||||
processor.EnqueueKeyDownEvent (Key.A);
|
||||
}
|
||||
});
|
||||
threads [t].Start ();
|
||||
}
|
||||
|
||||
// Wait for all threads to complete
|
||||
foreach (Thread thread in threads)
|
||||
{
|
||||
thread.Join ();
|
||||
}
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.Equal (threadCount * keysPerThread, receivedKeys.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Error Handling Tests
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueKeyDownEvent_WithInvalidKey_DoesNotThrow ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
// Act & Assert - Empty/null key should not throw
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
processor.EnqueueKeyDownEvent (Key.Empty);
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
});
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
531
Tests/UnitTestsParallelizable/Input/EnqueueMouseEventTests.cs
Normal file
531
Tests/UnitTestsParallelizable/Input/EnqueueMouseEventTests.cs
Normal file
@@ -0,0 +1,531 @@
|
||||
#nullable enable
|
||||
using System.Collections.Concurrent;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
/// <summary>
|
||||
/// Parallelizable unit tests for IInputProcessor.EnqueueMouseEvent.
|
||||
/// Tests validate the entire pipeline: MouseEventArgs → TInputRecord → Queue → ProcessQueue → Events.
|
||||
/// fully implemented in InputProcessorImpl (base class). Only WindowsInputProcessor has a working implementation.
|
||||
/// </summary>
|
||||
[Trait ("Category", "Input")]
|
||||
public class EnqueueMouseEventTests (ITestOutputHelper output)
|
||||
{
|
||||
private readonly ITestOutputHelper _output = output;
|
||||
|
||||
#region Mouse Event Sequencing Tests
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueMouseEvent_HandlesCompleteClickSequence ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
List<MouseEventArgs> receivedEvents = [];
|
||||
processor.MouseEvent += (_, e) => receivedEvents.Add (e);
|
||||
|
||||
// Act - Simulate a complete click: press → release → click
|
||||
processor.EnqueueMouseEvent (
|
||||
new()
|
||||
{
|
||||
Position = new (10, 5),
|
||||
Flags = MouseFlags.Button1Pressed
|
||||
});
|
||||
|
||||
processor.EnqueueMouseEvent (
|
||||
new()
|
||||
{
|
||||
Position = new (10, 5),
|
||||
Flags = MouseFlags.Button1Released
|
||||
});
|
||||
|
||||
// The MouseInterpreter in the processor should generate a clicked event
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
// We should see at least the pressed and released events
|
||||
Assert.True (receivedEvents.Count >= 2);
|
||||
Assert.Contains (receivedEvents, e => e.Flags.HasFlag (MouseFlags.Button1Pressed));
|
||||
Assert.Contains (receivedEvents, e => e.Flags.HasFlag (MouseFlags.Button1Released));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Thread Safety Tests
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueMouseEvent_IsThreadSafe ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
ConcurrentBag<MouseEventArgs> receivedEvents = [];
|
||||
processor.MouseEvent += (_, e) => receivedEvents.Add (e);
|
||||
|
||||
const int threadCount = 10;
|
||||
const int eventsPerThread = 100;
|
||||
Thread [] threads = new Thread [threadCount];
|
||||
|
||||
// Act - Enqueue mouse events from multiple threads
|
||||
for (var t = 0; t < threadCount; t++)
|
||||
{
|
||||
int threadId = t;
|
||||
|
||||
threads [t] = new (() =>
|
||||
{
|
||||
for (var i = 0; i < eventsPerThread; i++)
|
||||
{
|
||||
processor.EnqueueMouseEvent (
|
||||
new()
|
||||
{
|
||||
Position = new (threadId, i),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
});
|
||||
}
|
||||
});
|
||||
threads [t].Start ();
|
||||
}
|
||||
|
||||
// Wait for all threads to complete
|
||||
foreach (Thread thread in threads)
|
||||
{
|
||||
thread.Join ();
|
||||
}
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.Equal (threadCount * eventsPerThread, receivedEvents.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Simulates the input thread by manually draining FakeInput's internal queue
|
||||
/// and moving items to the InputBuffer. This is needed because tests don't
|
||||
/// start the actual input thread via Run().
|
||||
/// </summary>
|
||||
private static void SimulateInputThread (FakeInput fakeInput, ConcurrentQueue<ConsoleKeyInfo> inputBuffer)
|
||||
{
|
||||
// FakeInput's Peek() checks _testInput
|
||||
while (fakeInput.Peek ())
|
||||
{
|
||||
// Read() drains _testInput and returns items
|
||||
foreach (ConsoleKeyInfo item in fakeInput.Read ())
|
||||
{
|
||||
// Manually add to InputBuffer (simulating what Run() would do)
|
||||
inputBuffer.Enqueue (item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FakeInputProcessor EnqueueMouseEvent Tests
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueMouseEvent_AddsSingleMouseEventToQueue ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
List<MouseEventArgs> receivedEvents = [];
|
||||
processor.MouseEvent += (_, e) => receivedEvents.Add (e);
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new (10, 5),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
};
|
||||
|
||||
// Act
|
||||
processor.EnqueueMouseEvent (mouseEvent);
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert - Verify the mouse event made it through
|
||||
Assert.Single (receivedEvents);
|
||||
Assert.Equal (mouseEvent.Position, receivedEvents [0].Position);
|
||||
Assert.Equal (mouseEvent.Flags, receivedEvents [0].Flags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueMouseEvent_SupportsMultipleEvents ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
MouseEventArgs [] events =
|
||||
[
|
||||
new () { Position = new (10, 5), Flags = MouseFlags.Button1Pressed },
|
||||
new () { Position = new (10, 5), Flags = MouseFlags.Button1Released },
|
||||
new () { Position = new (15, 8), Flags = MouseFlags.ReportMousePosition },
|
||||
new () { Position = new (20, 10), Flags = MouseFlags.Button1Clicked }
|
||||
];
|
||||
|
||||
List<MouseEventArgs> receivedEvents = [];
|
||||
processor.MouseEvent += (_, e) => receivedEvents.Add (e);
|
||||
|
||||
// Act
|
||||
foreach (MouseEventArgs mouseEvent in events)
|
||||
{
|
||||
processor.EnqueueMouseEvent (mouseEvent);
|
||||
}
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
// The MouseInterpreter processes Button1Pressed followed by Button1Released and generates
|
||||
// an additional Button1Clicked event, so we expect 5 events total:
|
||||
// 1. Button1Pressed (original)
|
||||
// 2. Button1Released (original)
|
||||
// 3. Button1Clicked (generated by MouseInterpreter from press+release)
|
||||
// 4. ReportMousePosition (original)
|
||||
// 5. Button1Clicked (original)
|
||||
Assert.Equal (5, receivedEvents.Count);
|
||||
|
||||
// Verify the original events are present
|
||||
Assert.Contains (receivedEvents, e => e.Flags == MouseFlags.Button1Pressed && e.Position == new Point (10, 5));
|
||||
Assert.Contains (receivedEvents, e => e.Flags == MouseFlags.Button1Released && e.Position == new Point (10, 5));
|
||||
Assert.Contains (receivedEvents, e => e.Flags == MouseFlags.ReportMousePosition && e.Position == new Point (15, 8));
|
||||
|
||||
// There should be two clicked events: one generated, one original
|
||||
var clickedEvents = receivedEvents.Where (e => e.Flags == MouseFlags.Button1Clicked).ToList ();
|
||||
Assert.Equal (2, clickedEvents.Count);
|
||||
Assert.Contains (clickedEvents, e => e.Position == new Point (10, 5)); // Generated from press+release
|
||||
Assert.Contains (clickedEvents, e => e.Position == new Point (20, 10)); // Original
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (MouseFlags.Button1Clicked)]
|
||||
[InlineData (MouseFlags.Button2Clicked)]
|
||||
[InlineData (MouseFlags.Button3Clicked)]
|
||||
[InlineData (MouseFlags.Button4Clicked)]
|
||||
[InlineData (MouseFlags.Button1DoubleClicked)]
|
||||
[InlineData (MouseFlags.Button1TripleClicked)]
|
||||
public void FakeInput_EnqueueMouseEvent_SupportsAllButtonClicks (MouseFlags flags)
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new (10, 5),
|
||||
Flags = flags
|
||||
};
|
||||
|
||||
MouseEventArgs? receivedEvent = null;
|
||||
processor.MouseEvent += (_, e) => receivedEvent = e;
|
||||
|
||||
// Act
|
||||
processor.EnqueueMouseEvent (mouseEvent);
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull (receivedEvent);
|
||||
Assert.Equal (flags, receivedEvent.Flags);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (0, 0)]
|
||||
[InlineData (10, 5)]
|
||||
[InlineData (79, 24)] // Near screen edge (assuming 80x25)
|
||||
[InlineData (100, 100)] // Beyond typical screen
|
||||
public void FakeInput_EnqueueMouseEvent_PreservesPosition (int x, int y)
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new (x, y),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
};
|
||||
|
||||
MouseEventArgs? receivedEvent = null;
|
||||
processor.MouseEvent += (_, e) => receivedEvent = e;
|
||||
|
||||
// Act
|
||||
processor.EnqueueMouseEvent (mouseEvent);
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull (receivedEvent);
|
||||
Assert.Equal (x, receivedEvent.Position.X);
|
||||
Assert.Equal (y, receivedEvent.Position.Y);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (MouseFlags.ButtonShift)]
|
||||
[InlineData (MouseFlags.ButtonCtrl)]
|
||||
[InlineData (MouseFlags.ButtonAlt)]
|
||||
[InlineData (MouseFlags.ButtonShift | MouseFlags.ButtonCtrl)]
|
||||
[InlineData (MouseFlags.ButtonShift | MouseFlags.ButtonAlt)]
|
||||
[InlineData (MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt)]
|
||||
[InlineData (MouseFlags.ButtonShift | MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt)]
|
||||
public void FakeInput_EnqueueMouseEvent_PreservesModifiers (MouseFlags modifiers)
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new (10, 5),
|
||||
Flags = MouseFlags.Button1Clicked | modifiers
|
||||
};
|
||||
|
||||
MouseEventArgs? receivedEvent = null;
|
||||
processor.MouseEvent += (_, e) => receivedEvent = e;
|
||||
|
||||
// Act
|
||||
processor.EnqueueMouseEvent (mouseEvent);
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull (receivedEvent);
|
||||
Assert.True (receivedEvent.Flags.HasFlag (MouseFlags.Button1Clicked));
|
||||
|
||||
if (modifiers.HasFlag (MouseFlags.ButtonShift))
|
||||
{
|
||||
Assert.True (receivedEvent.Flags.HasFlag (MouseFlags.ButtonShift));
|
||||
}
|
||||
|
||||
if (modifiers.HasFlag (MouseFlags.ButtonCtrl))
|
||||
{
|
||||
Assert.True (receivedEvent.Flags.HasFlag (MouseFlags.ButtonCtrl));
|
||||
}
|
||||
|
||||
if (modifiers.HasFlag (MouseFlags.ButtonAlt))
|
||||
{
|
||||
Assert.True (receivedEvent.Flags.HasFlag (MouseFlags.ButtonAlt));
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (MouseFlags.WheeledUp)]
|
||||
[InlineData (MouseFlags.WheeledDown)]
|
||||
[InlineData (MouseFlags.WheeledLeft)]
|
||||
[InlineData (MouseFlags.WheeledRight)]
|
||||
public void FakeInput_EnqueueMouseEvent_SupportsMouseWheel (MouseFlags wheelFlag)
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new (10, 5),
|
||||
Flags = wheelFlag
|
||||
};
|
||||
|
||||
MouseEventArgs? receivedEvent = null;
|
||||
processor.MouseEvent += (_, e) => receivedEvent = e;
|
||||
|
||||
// Act
|
||||
processor.EnqueueMouseEvent (mouseEvent);
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull (receivedEvent);
|
||||
Assert.True (receivedEvent.Flags.HasFlag (wheelFlag));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueMouseEvent_SupportsMouseMove ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
List<MouseEventArgs> receivedEvents = [];
|
||||
processor.MouseEvent += (_, e) => receivedEvents.Add (e);
|
||||
|
||||
MouseEventArgs [] events =
|
||||
[
|
||||
new () { Position = new (0, 0), Flags = MouseFlags.ReportMousePosition },
|
||||
new () { Position = new (5, 5), Flags = MouseFlags.ReportMousePosition },
|
||||
new () { Position = new (10, 10), Flags = MouseFlags.ReportMousePosition }
|
||||
];
|
||||
|
||||
// Act
|
||||
foreach (MouseEventArgs mouseEvent in events)
|
||||
{
|
||||
processor.EnqueueMouseEvent (mouseEvent);
|
||||
}
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.Equal (3, receivedEvents.Count);
|
||||
Assert.Equal (new (0, 0), receivedEvents [0].Position);
|
||||
Assert.Equal (new (5, 5), receivedEvents [1].Position);
|
||||
Assert.Equal (new (10, 10), receivedEvents [2].Position);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region InputProcessor Pipeline Tests
|
||||
|
||||
[Fact]
|
||||
public void InputProcessor_EnqueueMouseEvent_DoesNotThrow ()
|
||||
{
|
||||
// Arrange
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
|
||||
// Don't set InputImpl (or set to non-testable)
|
||||
|
||||
// Act & Assert - Should not throw even if not implemented
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
processor.EnqueueMouseEvent (
|
||||
new()
|
||||
{
|
||||
Position = new (10, 5),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
});
|
||||
processor.ProcessQueue ();
|
||||
});
|
||||
|
||||
// The base implementation logs a critical message but doesn't throw
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InputProcessor_ProcessQueue_DrainsPendingMouseEvents ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
List<MouseEventArgs> receivedEvents = [];
|
||||
processor.MouseEvent += (_, e) => receivedEvents.Add (e);
|
||||
|
||||
// Act - Enqueue multiple events before processing
|
||||
processor.EnqueueMouseEvent (new() { Position = new (1, 1), Flags = MouseFlags.Button1Pressed });
|
||||
processor.EnqueueMouseEvent (new() { Position = new (2, 2), Flags = MouseFlags.ReportMousePosition });
|
||||
processor.EnqueueMouseEvent (new() { Position = new (3, 3), Flags = MouseFlags.Button1Released });
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert - After processing, all events should be received
|
||||
Assert.Empty (queue);
|
||||
Assert.Equal (3, receivedEvents.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Error Handling Tests
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueMouseEvent_WithInvalidEvent_DoesNotThrow ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
// Act & Assert - Empty/default mouse event should not throw
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
processor.EnqueueMouseEvent (new ());
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
});
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueMouseEvent_WithNegativePosition_DoesNotThrow ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
// Act & Assert - Negative positions should not throw
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
processor.EnqueueMouseEvent (
|
||||
new()
|
||||
{
|
||||
Position = new (-10, -5),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
});
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
});
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -591,4 +591,20 @@ public class KeyTests
|
||||
r.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Set_Key_Separator_With_Rune_Default_Ensure_Using_The_Default_Plus ()
|
||||
{
|
||||
Key key = new (Key.A.WithCtrl);
|
||||
Assert.Equal ((Rune)'+', Key.Separator);
|
||||
Assert.Equal ("Ctrl+A", key.ToString ());
|
||||
|
||||
// NOTE: This means this test can't be parallelized
|
||||
Key.Separator = new ('-');
|
||||
Assert.Equal ((Rune)'-', Key.Separator);
|
||||
Assert.Equal ("Ctrl-A", key.ToString ());
|
||||
|
||||
Key.Separator = new ();
|
||||
Assert.Equal ((Rune)'+', Key.Separator);
|
||||
Assert.Equal ("Ctrl+A", key.ToString ());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user