Fixes #3692++ - Rearchitects drivers (#3837)

This commit is contained in:
Thomas Nind
2025-02-28 19:09:29 +00:00
committed by GitHub
parent 3a240ecbe5
commit c88c772462
101 changed files with 7662 additions and 658 deletions

View File

@@ -58,47 +58,47 @@ public class MainLoopTests
ml.AddIdle (fnTrue);
ml.AddIdle (fnFalse);
Assert.Equal (2, ml.IdleHandlers.Count);
Assert.Equal (fnTrue, ml.IdleHandlers [0]);
Assert.NotEqual (fnFalse, ml.IdleHandlers [0]);
Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers [0]);
Assert.NotEqual (fnFalse, ml.TimedEvents.IdleHandlers[0]);
Assert.True (ml.RemoveIdle (fnTrue));
Assert.Single (ml.IdleHandlers);
Assert.True (ml.TimedEvents.RemoveIdle (fnTrue));
Assert.Single (ml.TimedEvents.IdleHandlers);
// BUGBUG: This doesn't throw or indicate an error. Ideally RemoveIdle would either
// throw an exception in this case, or return an error.
// No. Only need to return a boolean.
Assert.False (ml.RemoveIdle (fnTrue));
Assert.False (ml.TimedEvents.RemoveIdle (fnTrue));
Assert.True (ml.RemoveIdle (fnFalse));
Assert.True (ml.TimedEvents.RemoveIdle (fnFalse));
// BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either
// throw an exception in this case, or return an error.
// No. Only need to return a boolean.
Assert.False (ml.RemoveIdle (fnFalse));
Assert.False (ml.TimedEvents.RemoveIdle (fnFalse));
// Add again, but with dupe
ml.AddIdle (fnTrue);
ml.AddIdle (fnTrue);
Assert.Equal (2, ml.IdleHandlers.Count);
Assert.Equal (fnTrue, ml.IdleHandlers [0]);
Assert.True (ml.IdleHandlers [0] ());
Assert.Equal (fnTrue, ml.IdleHandlers [1]);
Assert.True (ml.IdleHandlers [1] ());
Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers[0]);
Assert.True (ml.TimedEvents.IdleHandlers[0] ());
Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers[1]);
Assert.True (ml.TimedEvents.IdleHandlers[1] ());
Assert.True (ml.RemoveIdle (fnTrue));
Assert.Single (ml.IdleHandlers);
Assert.Equal (fnTrue, ml.IdleHandlers [0]);
Assert.NotEqual (fnFalse, ml.IdleHandlers [0]);
Assert.True (ml.TimedEvents.RemoveIdle (fnTrue));
Assert.Single (ml.TimedEvents.IdleHandlers);
Assert.Equal (fnTrue, ml.TimedEvents.IdleHandlers[0]);
Assert.NotEqual (fnFalse, ml.TimedEvents.IdleHandlers[0]);
Assert.True (ml.RemoveIdle (fnTrue));
Assert.Empty (ml.IdleHandlers);
Assert.True (ml.TimedEvents.RemoveIdle (fnTrue));
Assert.Empty (ml.TimedEvents.IdleHandlers);
// BUGBUG: This doesn't throw an exception or indicate an error. Ideally RemoveIdle would either
// throw an exception in this case, or return an error.
// No. Only need to return a boolean.
Assert.False (ml.RemoveIdle (fnTrue));
Assert.False (ml.TimedEvents.RemoveIdle (fnTrue));
}
[Fact]
@@ -153,9 +153,9 @@ public class MainLoopTests
ml.AddIdle (fn1);
ml.AddIdle (fn1);
ml.Run ();
Assert.True (ml.RemoveIdle (fnStop));
Assert.False (ml.RemoveIdle (fn1));
Assert.False (ml.RemoveIdle (fn1));
Assert.True (ml.TimedEvents.RemoveIdle (fnStop));
Assert.False (ml.TimedEvents.RemoveIdle (fn1));
Assert.False (ml.TimedEvents.RemoveIdle (fn1));
Assert.Equal (2, functionCalled);
}
@@ -178,20 +178,20 @@ public class MainLoopTests
ml.AddIdle (fn);
ml.RunIteration ();
Assert.Equal (2, functionCalled);
Assert.Equal (2, ml.IdleHandlers.Count);
Assert.Equal (2, ml.TimedEvents.IdleHandlers.Count);
functionCalled = 0;
Assert.True (ml.RemoveIdle (fn));
Assert.Single (ml.IdleHandlers);
Assert.True (ml.TimedEvents.RemoveIdle (fn));
Assert.Single (ml.TimedEvents.IdleHandlers);
ml.RunIteration ();
Assert.Equal (1, functionCalled);
functionCalled = 0;
Assert.True (ml.RemoveIdle (fn));
Assert.Empty (ml.IdleHandlers);
Assert.True (ml.TimedEvents.RemoveIdle (fn));
Assert.Empty (ml.TimedEvents.IdleHandlers);
ml.RunIteration ();
Assert.Equal (0, functionCalled);
Assert.False (ml.RemoveIdle (fn));
Assert.False (ml.TimedEvents.RemoveIdle (fn));
}
[Fact]
@@ -209,7 +209,7 @@ public class MainLoopTests
};
ml.AddIdle (fn);
Assert.True (ml.RemoveIdle (fn));
Assert.True (ml.TimedEvents.RemoveIdle (fn));
ml.RunIteration ();
Assert.Equal (0, functionCalled);
}
@@ -230,13 +230,13 @@ public class MainLoopTests
return true;
};
object token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
object token = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
Assert.True (ml.RemoveTimeout (token));
Assert.True (ml.TimedEvents.RemoveTimeout (token));
// BUGBUG: This should probably fault?
// Must return a boolean.
Assert.False (ml.RemoveTimeout (token));
Assert.False (ml.TimedEvents.RemoveTimeout (token));
}
[Fact]
@@ -260,8 +260,8 @@ public class MainLoopTests
return true;
};
var task1 = new Task (() => token1 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback));
var task2 = new Task (() => token2 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback));
var task1 = new Task (() => token1 = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback));
var task2 = new Task (() => token2 = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback));
Assert.Null (token1);
Assert.Null (token2);
task1.Start ();
@@ -270,8 +270,8 @@ public class MainLoopTests
Assert.NotNull (token1);
Assert.NotNull (token2);
await Task.WhenAll (task1, task2);
Assert.True (ml.RemoveTimeout (token1));
Assert.True (ml.RemoveTimeout (token2));
Assert.True (ml.TimedEvents.RemoveTimeout (token1));
Assert.True (ml.TimedEvents.RemoveTimeout (token2));
Assert.Equal (2, callbackCount);
}
@@ -297,15 +297,15 @@ public class MainLoopTests
object sender = null;
TimeoutEventArgs args = null;
ml.TimeoutAdded += (s, e) =>
ml.TimedEvents.TimeoutAdded += (s, e) =>
{
sender = s;
args = e;
};
object token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
object token = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
Assert.Same (ml, sender);
Assert.Same (ml.TimedEvents, sender);
Assert.NotNull (args.Timeout);
Assert.True (args.Ticks - originTicks >= 100 * TimeSpan.TicksPerMillisecond);
}
@@ -332,14 +332,14 @@ public class MainLoopTests
};
Parallel.Invoke (
() => token1 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback),
() => token2 = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback)
() => token1 = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback),
() => token2 = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback)
);
ml.Run ();
Assert.NotNull (token1);
Assert.NotNull (token2);
Assert.True (ml.RemoveTimeout (token1));
Assert.True (ml.RemoveTimeout (token2));
Assert.True (ml.TimedEvents.RemoveTimeout (token1));
Assert.True (ml.TimedEvents.RemoveTimeout (token2));
Assert.Equal (2, callbackCount);
}
@@ -375,8 +375,8 @@ public class MainLoopTests
return true;
};
object token = ml.AddTimeout (ms, callback);
Assert.True (ml.RemoveTimeout (token));
object token = ml.TimedEvents.AddTimeout (ms, callback);
Assert.True (ml.TimedEvents.RemoveTimeout (token));
ml.Run ();
Assert.Equal (0, callbackCount);
}
@@ -413,11 +413,11 @@ public class MainLoopTests
return false;
};
object token = ml.AddTimeout (ms, callback);
object token = ml.TimedEvents.AddTimeout (ms, callback);
ml.Run ();
Assert.Equal (1, callbackCount);
Assert.Equal (10, stopCount);
Assert.False (ml.RemoveTimeout (token));
Assert.False (ml.TimedEvents.RemoveTimeout (token));
}
[Fact]
@@ -436,9 +436,9 @@ public class MainLoopTests
return true;
};
object token = ml.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
object token = ml.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (ms), callback);
ml.Run ();
Assert.True (ml.RemoveTimeout (token));
Assert.True (ml.TimedEvents.RemoveTimeout (token));
Assert.Equal (1, callbackCount);
}
@@ -461,7 +461,7 @@ public class MainLoopTests
return true;
};
object token = ml.AddTimeout (ms, callback);
object token = ml.TimedEvents.AddTimeout (ms, callback);
watch.Start ();
ml.Run ();
@@ -469,7 +469,7 @@ public class MainLoopTests
// https://github.com/xunit/assert.xunit/pull/25
Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
Assert.True (ml.RemoveTimeout (token));
Assert.True (ml.TimedEvents.RemoveTimeout (token));
Assert.Equal (1, callbackCount);
}
@@ -495,7 +495,7 @@ public class MainLoopTests
return true;
};
object token = ml.AddTimeout (ms, callback);
object token = ml.TimedEvents.AddTimeout (ms, callback);
watch.Start ();
ml.Run ();
@@ -503,7 +503,7 @@ public class MainLoopTests
// https://github.com/xunit/assert.xunit/pull/25
Assert.Equal (ms * callbackCount, watch.Elapsed, new MillisecondTolerance (100));
Assert.True (ml.RemoveTimeout (token));
Assert.True (ml.TimedEvents.RemoveTimeout (token));
Assert.Equal (2, callbackCount);
}
@@ -511,7 +511,7 @@ public class MainLoopTests
public void CheckTimersAndIdleHandlers_NoTimers_Returns_False ()
{
var ml = new MainLoop (new FakeMainLoop ());
bool retVal = ml.CheckTimersAndIdleHandlers (out int waitTimeOut);
bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut);
Assert.False (retVal);
Assert.Equal (-1, waitTimeOut);
}
@@ -523,7 +523,7 @@ public class MainLoopTests
Func<bool> fnTrue = () => true;
ml.AddIdle (fnTrue);
bool retVal = ml.CheckTimersAndIdleHandlers (out int waitTimeOut);
bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut);
Assert.True (retVal);
Assert.Equal (-1, waitTimeOut);
}
@@ -536,8 +536,8 @@ public class MainLoopTests
static bool Callback () { return false; }
_ = ml.AddTimeout (ms, Callback);
bool retVal = ml.CheckTimersAndIdleHandlers (out int waitTimeOut);
_ = ml.TimedEvents.AddTimeout (ms, Callback);
bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut);
Assert.True (retVal);
@@ -553,9 +553,9 @@ public class MainLoopTests
static bool Callback () { return false; }
_ = ml.AddTimeout (ms, Callback);
_ = ml.AddTimeout (ms, Callback);
bool retVal = ml.CheckTimersAndIdleHandlers (out int waitTimeOut);
_ = ml.TimedEvents.AddTimeout (ms, Callback);
_ = ml.TimedEvents.AddTimeout (ms, Callback);
bool retVal = ml.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeOut);
Assert.True (retVal);
@@ -600,8 +600,8 @@ public class MainLoopTests
ml.AddIdle (fnStop);
ml.AddIdle (fn1);
ml.Run ();
Assert.True (ml.RemoveIdle (fnStop));
Assert.False (ml.RemoveIdle (fn1));
Assert.True (ml.TimedEvents.RemoveIdle (fnStop));
Assert.False (ml.TimedEvents.RemoveIdle (fn1));
Assert.Equal (10, functionCalled);
Assert.Equal (20, stopCount);
@@ -612,8 +612,8 @@ public class MainLoopTests
{
var testMainloop = new TestMainloop ();
var mainloop = new MainLoop (testMainloop);
Assert.Empty (mainloop._timeouts);
Assert.Empty (mainloop._idleHandlers);
Assert.Empty (mainloop.TimedEvents.Timeouts);
Assert.Empty (mainloop.TimedEvents.IdleHandlers);
Assert.NotNull (
new Timeout { Span = new TimeSpan (), Callback = () => true }
@@ -748,7 +748,7 @@ public class MainLoopTests
return true;
};
Assert.False (ml.RemoveIdle (fn));
Assert.False (ml.TimedEvents.RemoveIdle (fn));
ml.RunIteration ();
Assert.Equal (0, functionCalled);
}
@@ -774,7 +774,7 @@ public class MainLoopTests
ml.AddIdle (fn);
ml.Run ();
Assert.True (ml.RemoveIdle (fn));
Assert.True (ml.TimedEvents.RemoveIdle (fn));
Assert.Equal (10, functionCalled);
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UnitTests.ConsoleDrivers;
public class AnsiKeyboardParserTests
{
private readonly AnsiKeyboardParser _parser = new ();
public static IEnumerable<object []> GetKeyboardTestData ()
{
// Test data for various ANSI escape sequences and their expected Key values
yield return new object [] { "\u001b[A", Key.CursorUp };
yield return new object [] { "\u001b[B", Key.CursorDown };
yield return new object [] { "\u001b[C", Key.CursorRight };
yield return new object [] { "\u001b[D", Key.CursorLeft };
// Valid inputs with modifiers
yield return new object [] { "\u001b[1;2A", Key.CursorUp.WithShift };
yield return new object [] { "\u001b[1;3A", Key.CursorUp.WithAlt };
yield return new object [] { "\u001b[1;4A", Key.CursorUp.WithAlt.WithShift };
yield return new object [] { "\u001b[1;5A", Key.CursorUp.WithCtrl };
yield return new object [] { "\u001b[1;6A", Key.CursorUp.WithCtrl.WithShift };
yield return new object [] { "\u001b[1;7A", Key.CursorUp.WithCtrl.WithAlt };
yield return new object [] { "\u001b[1;8A", Key.CursorUp.WithCtrl.WithAlt.WithShift };
yield return new object [] { "\u001b[1;2B", Key.CursorDown.WithShift };
yield return new object [] { "\u001b[1;3B", Key.CursorDown.WithAlt };
yield return new object [] { "\u001b[1;4B", Key.CursorDown.WithAlt.WithShift };
yield return new object [] { "\u001b[1;5B", Key.CursorDown.WithCtrl };
yield return new object [] { "\u001b[1;6B", Key.CursorDown.WithCtrl.WithShift };
yield return new object [] { "\u001b[1;7B", Key.CursorDown.WithCtrl.WithAlt };
yield return new object [] { "\u001b[1;8B", Key.CursorDown.WithCtrl.WithAlt.WithShift };
yield return new object [] { "\u001b[1;2C", Key.CursorRight.WithShift };
yield return new object [] { "\u001b[1;3C", Key.CursorRight.WithAlt };
yield return new object [] { "\u001b[1;4C", Key.CursorRight.WithAlt.WithShift };
yield return new object [] { "\u001b[1;5C", Key.CursorRight.WithCtrl };
yield return new object [] { "\u001b[1;6C", Key.CursorRight.WithCtrl.WithShift };
yield return new object [] { "\u001b[1;7C", Key.CursorRight.WithCtrl.WithAlt };
yield return new object [] { "\u001b[1;8C", Key.CursorRight.WithCtrl.WithAlt.WithShift };
yield return new object [] { "\u001b[1;2D", Key.CursorLeft.WithShift };
yield return new object [] { "\u001b[1;3D", Key.CursorLeft.WithAlt };
yield return new object [] { "\u001b[1;4D", Key.CursorLeft.WithAlt.WithShift };
yield return new object [] { "\u001b[1;5D", Key.CursorLeft.WithCtrl };
yield return new object [] { "\u001b[1;6D", Key.CursorLeft.WithCtrl.WithShift };
yield return new object [] { "\u001b[1;7D", Key.CursorLeft.WithCtrl.WithAlt };
yield return new object [] { "\u001b[1;8D", Key.CursorLeft.WithCtrl.WithAlt.WithShift };
// Invalid inputs
yield return new object [] { "\u001b[Z", null };
yield return new object [] { "\u001b[invalid", null };
yield return new object [] { "\u001b[1", null };
yield return new object [] { "\u001b[AB", null };
yield return new object [] { "\u001b[;A", null };
}
// Consolidated test for all keyboard events (e.g., arrow keys)
[Theory]
[MemberData (nameof (GetKeyboardTestData))]
public void ProcessKeyboardInput_ReturnsCorrectKey (string input, Key? expectedKey)
{
// Act
Key? result = _parser.IsKeyboard (input)?.GetKey (input);
// Assert
Assert.Equal (expectedKey, result); // Verify the returned key matches the expected one
}
}

View File

@@ -0,0 +1,42 @@
namespace UnitTests.ConsoleDrivers;
public class AnsiMouseParserTests
{
private readonly AnsiMouseParser _parser;
public AnsiMouseParserTests () { _parser = new (); }
// Consolidated test for all mouse events: button press/release, wheel scroll, position, modifiers
[Theory]
[InlineData ("\u001b[<0;100;200M", 99, 199, MouseFlags.Button1Pressed)] // Button 1 Pressed
[InlineData ("\u001b[<0;150;250m", 149, 249, MouseFlags.Button1Released)] // Button 1 Released
[InlineData ("\u001b[<1;120;220M", 119, 219, MouseFlags.Button2Pressed)] // Button 2 Pressed
[InlineData ("\u001b[<1;180;280m", 179, 279, MouseFlags.Button2Released)] // Button 2 Released
[InlineData ("\u001b[<2;200;300M", 199, 299, MouseFlags.Button3Pressed)] // Button 3 Pressed
[InlineData ("\u001b[<2;250;350m", 249, 349, MouseFlags.Button3Released)] // Button 3 Released
[InlineData ("\u001b[<64;100;200M", 99, 199, MouseFlags.WheeledUp)] // Wheel Scroll Up
[InlineData ("\u001b[<65;150;250m", 149, 249, MouseFlags.WheeledDown)] // Wheel Scroll Down
[InlineData ("\u001b[<39;100;200m", 99, 199, MouseFlags.ButtonShift | MouseFlags.ReportMousePosition)] // Mouse Position (No Button)
[InlineData ("\u001b[<43;120;240m", 119, 239, MouseFlags.ButtonAlt | MouseFlags.ReportMousePosition)] // Mouse Position (No Button)
[InlineData ("\u001b[<8;100;200M", 99, 199, MouseFlags.Button1Pressed | MouseFlags.ButtonAlt)] // Button 1 Pressed + Alt
[InlineData ("\u001b[<invalid;100;200M", 0, 0, MouseFlags.None)] // Invalid Input (Expecting null)
[InlineData ("\u001b[<100;200;300Z", 0, 0, MouseFlags.None)] // Invalid Input (Expecting null)
[InlineData ("\u001b[<invalidInput>", 0, 0, MouseFlags.None)] // Invalid Input (Expecting null)
public void ProcessMouseInput_ReturnsCorrectFlags (string input, int expectedX, int expectedY, MouseFlags expectedFlags)
{
// Act
MouseEventArgs result = _parser.ProcessMouseInput (input);
// Assert
if (expectedFlags == MouseFlags.None)
{
Assert.Null (result); // Expect null for invalid inputs
}
else
{
Assert.NotNull (result); // Expect non-null result for valid inputs
Assert.Equal (new (expectedX, expectedY), result!.Position); // Verify position
Assert.Equal (expectedFlags, result.Flags); // Verify flags
}
}
}

View File

@@ -153,7 +153,7 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
null,
new []
{
new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingBracket,string.Empty)
new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingEscapeSequence,string.Empty)
}
];
@@ -163,13 +163,13 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
'c',
new []
{
new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingBracket,string.Empty),
new StepExpectation ('H',AnsiResponseParserState.Normal,"\u001bH"), // H is known terminator and not expected one so here we release both chars
new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingBracket,string.Empty),
new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingEscapeSequence,string.Empty),
new StepExpectation ('H',AnsiResponseParserState.InResponse,string.Empty), // H is known terminator and not expected one so here we release both chars
new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingEscapeSequence,"\u001bH"),
new StepExpectation ('[',AnsiResponseParserState.InResponse,string.Empty),
new StepExpectation ('0',AnsiResponseParserState.InResponse,string.Empty),
new StepExpectation ('c',AnsiResponseParserState.Normal,string.Empty,"\u001b[0c"), // c is expected terminator so here we swallow input and populate expected response
new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingBracket,string.Empty),
new StepExpectation ('\u001b',AnsiResponseParserState.ExpectingEscapeSequence,string.Empty),
}
];
}
@@ -227,8 +227,10 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
{
parser.ExpectResponse (terminator.Value.ToString (),(s)=> response = s,null, false);
}
int step= 0;
foreach (var state in expectedStates)
{
step++;
// If we expect the response to be detected at this step
if (!string.IsNullOrWhiteSpace (state.ExpectedAnsiResponse))
{
@@ -247,6 +249,8 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
// And after passing input it shuld be the expected value
Assert.Equal (state.ExpectedAnsiResponse, response);
}
output.WriteLine ($"Step {step} passed");
}
}
@@ -260,8 +264,8 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
AssertConsumed (input,ref i);
// We should know when the state changed
Assert.Equal (AnsiResponseParserState.ExpectingBracket, _parser1.State);
Assert.Equal (AnsiResponseParserState.ExpectingBracket, _parser2.State);
Assert.Equal (AnsiResponseParserState.ExpectingEscapeSequence, _parser1.State);
Assert.Equal (AnsiResponseParserState.ExpectingEscapeSequence, _parser2.State);
Assert.Equal (DateTime.Now.Date, _parser1.StateChangedAt.Date);
Assert.Equal (DateTime.Now.Date, _parser2.StateChangedAt.Date);
@@ -289,35 +293,6 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
AssertManualReleaseIs ("\u001b",1);
}
[Fact]
public void TwoExcapesInARowWithTextBetween ()
{
// Example user presses Esc key and types at the speed of light (normally the consumer should be handling Esc timeout)
// then a DAR comes in.
string input = "\u001bfish\u001b";
int i = 0;
// First Esc gets grabbed
AssertConsumed (input, ref i); // Esc
Assert.Equal (AnsiResponseParserState.ExpectingBracket,_parser1.State);
Assert.Equal (AnsiResponseParserState.ExpectingBracket, _parser2.State);
// Because next char is 'f' we do not see a bracket so release both
AssertReleased (input, ref i, "\u001bf", 0,1); // f
Assert.Equal (AnsiResponseParserState.Normal, _parser1.State);
Assert.Equal (AnsiResponseParserState.Normal, _parser2.State);
AssertReleased (input, ref i,"i",2);
AssertReleased (input, ref i, "s", 3);
AssertReleased (input, ref i, "h", 4);
AssertConsumed (input, ref i); // Second Esc
// Assume 50ms or something has passed, lets force release as no new content
AssertManualReleaseIs ("\u001b", 5);
}
[Fact]
public void TestLateResponses ()
{
@@ -474,6 +449,191 @@ public class AnsiResponseParserTests (ITestOutputHelper output)
Assert.Equal (expectedUnknownResponses, unknownResponses);
}
[Fact]
public void ParserDetectsMouse ()
{
// ANSI escape sequence for mouse down (using a generic format example)
const string MOUSE_DOWN = "\u001B[<0;12;32M";
// ANSI escape sequence for Device Attribute Response (e.g., Terminal identifying itself)
const string DEVICE_ATTRIBUTE_RESPONSE = "\u001B[?1;2c";
// ANSI escape sequence for mouse up (using a generic format example)
const string MOUSE_UP = "\u001B[<0;25;50m";
var parser = new AnsiResponseParser ();
parser.HandleMouse = true;
string? foundDar = null;
List<MouseEventArgs> mouseEventArgs = new ();
parser.Mouse += (s, e) => mouseEventArgs.Add (e);
parser.ExpectResponse ("c", (dar) => foundDar = dar, null, false);
var released = parser.ProcessInput ("a" + MOUSE_DOWN + "asdf" + DEVICE_ATTRIBUTE_RESPONSE + "bbcc" + MOUSE_UP + "sss");
Assert.Equal ("aasdfbbccsss", released);
Assert.Equal (2, mouseEventArgs.Count);
Assert.NotNull (foundDar);
Assert.Equal (DEVICE_ATTRIBUTE_RESPONSE,foundDar);
Assert.True (mouseEventArgs [0].IsPressed);
// Mouse positions in ANSI are 1 based so actual Terminal.Gui Screen positions are x-1,y-1
Assert.Equal (11,mouseEventArgs [0].Position.X);
Assert.Equal (31, mouseEventArgs [0].Position.Y);
Assert.True (mouseEventArgs [1].IsReleased);
Assert.Equal (24, mouseEventArgs [1].Position.X);
Assert.Equal (49, mouseEventArgs [1].Position.Y);
}
[Fact]
public void ParserDetectsKeyboard ()
{
// ANSI escape sequence for cursor left
const string LEFT = "\u001b[D";
// ANSI escape sequence for Device Attribute Response (e.g., Terminal identifying itself)
const string DEVICE_ATTRIBUTE_RESPONSE = "\u001B[?1;2c";
// ANSI escape sequence for cursor up (while shift held down)
const string SHIFT_UP = "\u001b[1;2A";
var parser = new AnsiResponseParser ();
parser.HandleKeyboard = true;
string? foundDar = null;
List<Key> keys = new ();
parser.Keyboard += (s, e) => keys.Add (e);
parser.ExpectResponse ("c", (dar) => foundDar = dar, null, false);
var released = parser.ProcessInput ("a" + LEFT + "asdf" + DEVICE_ATTRIBUTE_RESPONSE + "bbcc" + SHIFT_UP + "sss");
Assert.Equal ("aasdfbbccsss", released);
Assert.Equal (2, keys.Count);
Assert.NotNull (foundDar);
Assert.Equal (DEVICE_ATTRIBUTE_RESPONSE, foundDar);
Assert.Equal (Key.CursorLeft,keys [0]);
Assert.Equal (Key.CursorUp.WithShift, keys [1]);
}
public static IEnumerable<object []> ParserDetects_FunctionKeys_Cases ()
{
// These are VT100 escape codes for F1-4
yield return
[
"\u001bOP",
Key.F1
];
yield return
[
"\u001bOQ",
Key.F2
];
yield return
[
"\u001bOR",
Key.F3
];
yield return
[
"\u001bOS",
Key.F4
];
// These are also F keys
yield return [
"\u001b[11~",
Key.F1
];
yield return [
"\u001b[12~",
Key.F2
];
yield return [
"\u001b[13~",
Key.F3
];
yield return [
"\u001b[14~",
Key.F4
];
yield return [
"\u001b[15~",
Key.F5
];
yield return [
"\u001b[17~",
Key.F6
];
yield return [
"\u001b[18~",
Key.F7
];
yield return [
"\u001b[19~",
Key.F8
];
yield return [
"\u001b[20~",
Key.F9
];
yield return [
"\u001b[21~",
Key.F10
];
yield return [
"\u001b[23~",
Key.F11
];
yield return [
"\u001b[24~",
Key.F12
];
}
[MemberData (nameof (ParserDetects_FunctionKeys_Cases))]
[Theory]
public void ParserDetects_FunctionKeys (string input, Key expectedKey)
{
var parser = new AnsiResponseParser ();
parser.HandleKeyboard = true;
List<Key> keys = new ();
parser.Keyboard += (s, e) => keys.Add (e);
foreach (var ch in input.ToCharArray ())
{
parser.ProcessInput (new (ch,1));
}
var k = Assert.Single (keys);
Assert.Equal (k,expectedKey);
}
private Tuple<char, int> [] StringToBatch (string batch)
{
return batch.Select ((k) => Tuple.Create (k, tIndex++)).ToArray ();

View File

@@ -221,7 +221,8 @@ public class ConsoleDriverTests
driver.Cols = 120;
driver.Rows = 40;
driver.OnSizeChanged (new SizeChangedEventArgs (new (driver.Cols, driver.Rows)));
((ConsoleDriver)driver).OnSizeChanged (new SizeChangedEventArgs (new (driver.Cols, driver.Rows)));
Assert.Equal (120, driver.Cols);
Assert.Equal (40, driver.Rows);
Assert.True (wasTerminalResized);

View File

@@ -52,7 +52,7 @@ public class MainLoopDriverTests
var mainLoop = new MainLoop (mainLoopDriver);
var callbackInvoked = false;
object token = mainLoop.AddTimeout (
object token = mainLoop.TimedEvents.AddTimeout (
TimeSpan.FromMilliseconds (100),
() =>
{
@@ -88,7 +88,7 @@ public class MainLoopDriverTests
var mainLoop = new MainLoop (mainLoopDriver);
mainLoop.AddIdle (() => false);
bool result = mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout);
bool result = mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout);
Assert.True (result);
Assert.Equal (-1, waitTimeout);
@@ -111,7 +111,7 @@ public class MainLoopDriverTests
var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
var mainLoop = new MainLoop (mainLoopDriver);
bool result = mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout);
bool result = mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout);
Assert.False (result);
Assert.Equal (-1, waitTimeout);
@@ -134,8 +134,8 @@ public class MainLoopDriverTests
var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
var mainLoop = new MainLoop (mainLoopDriver);
mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => false);
bool result = mainLoop.CheckTimersAndIdleHandlers (out int waitTimeout);
mainLoop.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (100), () => false);
bool result = mainLoop.TimedEvents.CheckTimersAndIdleHandlers (out int waitTimeout);
Assert.True (result);
Assert.True (waitTimeout >= 0);
@@ -158,8 +158,8 @@ public class MainLoopDriverTests
// Check default values
Assert.NotNull (mainLoop);
Assert.Equal (mainLoopDriver, mainLoop.MainLoopDriver);
Assert.Empty (mainLoop.IdleHandlers);
Assert.Empty (mainLoop.Timeouts);
Assert.Empty (mainLoop.TimedEvents.IdleHandlers);
Assert.Empty (mainLoop.TimedEvents.Timeouts);
Assert.False (mainLoop.Running);
// Clean up
@@ -168,8 +168,8 @@ public class MainLoopDriverTests
// TODO: It'd be nice if we could really verify IMainLoopDriver.TearDown was called
// and that it was actually cleaned up.
Assert.Null (mainLoop.MainLoopDriver);
Assert.Empty (mainLoop.IdleHandlers);
Assert.Empty (mainLoop.Timeouts);
Assert.Empty (mainLoop.TimedEvents.IdleHandlers);
Assert.Empty (mainLoop.TimedEvents.Timeouts);
Assert.False (mainLoop.Running);
}
@@ -186,7 +186,7 @@ public class MainLoopDriverTests
var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
var mainLoop = new MainLoop (mainLoopDriver);
bool result = mainLoop.RemoveIdle (() => false);
bool result = mainLoop.TimedEvents.RemoveIdle (() => false);
Assert.False (result);
mainLoop.Dispose ();
@@ -208,7 +208,7 @@ public class MainLoopDriverTests
bool IdleHandler () { return false; }
Func<bool> token = mainLoop.AddIdle (IdleHandler);
bool result = mainLoop.RemoveIdle (token);
bool result = mainLoop.TimedEvents.RemoveIdle (token);
Assert.True (result);
mainLoop.Dispose ();
@@ -227,7 +227,7 @@ public class MainLoopDriverTests
var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
var mainLoop = new MainLoop (mainLoopDriver);
bool result = mainLoop.RemoveTimeout (new object ());
bool result = mainLoop.TimedEvents.RemoveTimeout (new object ());
Assert.False (result);
}
@@ -245,8 +245,8 @@ public class MainLoopDriverTests
var mainLoopDriver = (IMainLoopDriver)Activator.CreateInstance (mainLoopDriverType, driver);
var mainLoop = new MainLoop (mainLoopDriver);
object token = mainLoop.AddTimeout (TimeSpan.FromMilliseconds (100), () => false);
bool result = mainLoop.RemoveTimeout (token);
object token = mainLoop.TimedEvents.AddTimeout (TimeSpan.FromMilliseconds (100), () => false);
bool result = mainLoop.TimedEvents.RemoveTimeout (token);
Assert.True (result);
mainLoop.Dispose ();

View File

@@ -0,0 +1,287 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Moq;
namespace UnitTests.ConsoleDrivers.V2;
public class ApplicationV2Tests
{
private ApplicationV2 NewApplicationV2 ()
{
var netInput = new Mock<INetInput> ();
SetupRunInputMockMethodToBlock (netInput);
var winInput = new Mock<IWindowsInput> ();
SetupRunInputMockMethodToBlock (winInput);
return new (
()=>netInput.Object,
Mock.Of<IConsoleOutput>,
() => winInput.Object,
Mock.Of<IConsoleOutput>);
}
[Fact]
public void TestInit_CreatesKeybindings ()
{
var v2 = NewApplicationV2();
Application.KeyBindings.Clear();
Assert.Empty(Application.KeyBindings.GetBindings ());
v2.Init ();
Assert.NotEmpty (Application.KeyBindings.GetBindings ());
v2.Shutdown ();
}
[Fact]
public void TestInit_DriverIsFacade ()
{
var v2 = NewApplicationV2();
Assert.Null (Application.Driver);
v2.Init ();
Assert.NotNull (Application.Driver);
var type = Application.Driver.GetType ();
Assert.True(type.IsGenericType);
Assert.True (type.GetGenericTypeDefinition () == typeof (ConsoleDriverFacade<>));
v2.Shutdown ();
Assert.Null (Application.Driver);
}
[Fact]
public void TestInit_ExplicitlyRequestWin ()
{
var netInput = new Mock<INetInput> (MockBehavior.Strict);
var netOutput = new Mock<IConsoleOutput> (MockBehavior.Strict);
var winInput = new Mock<IWindowsInput> (MockBehavior.Strict);
var winOutput = new Mock<IConsoleOutput> (MockBehavior.Strict);
winInput.Setup (i => i.Initialize (It.IsAny<ConcurrentQueue<WindowsConsole.InputRecord>> ()))
.Verifiable(Times.Once);
SetupRunInputMockMethodToBlock (winInput);
winInput.Setup (i=>i.Dispose ())
.Verifiable(Times.Once);
winOutput.Setup (i => i.Dispose ())
.Verifiable (Times.Once);
var v2 = new ApplicationV2 (
()=> netInput.Object,
() => netOutput.Object,
() => winInput.Object,
() => winOutput.Object);
Assert.Null (Application.Driver);
v2.Init (null,"v2win");
Assert.NotNull (Application.Driver);
var type = Application.Driver.GetType ();
Assert.True (type.IsGenericType);
Assert.True (type.GetGenericTypeDefinition () == typeof (ConsoleDriverFacade<>));
v2.Shutdown ();
Assert.Null (Application.Driver);
winInput.VerifyAll();
}
[Fact]
public void TestInit_ExplicitlyRequestNet ()
{
var netInput = new Mock<INetInput> (MockBehavior.Strict);
var netOutput = new Mock<IConsoleOutput> (MockBehavior.Strict);
var winInput = new Mock<IWindowsInput> (MockBehavior.Strict);
var winOutput = new Mock<IConsoleOutput> (MockBehavior.Strict);
netInput.Setup (i => i.Initialize (It.IsAny<ConcurrentQueue<ConsoleKeyInfo>> ()))
.Verifiable (Times.Once);
SetupRunInputMockMethodToBlock (netInput);
netInput.Setup (i => i.Dispose ())
.Verifiable (Times.Once);
netOutput.Setup (i => i.Dispose ())
.Verifiable (Times.Once);
var v2 = new ApplicationV2 (
() => netInput.Object,
() => netOutput.Object,
() => winInput.Object,
() => winOutput.Object);
Assert.Null (Application.Driver);
v2.Init (null, "v2net");
Assert.NotNull (Application.Driver);
var type = Application.Driver.GetType ();
Assert.True (type.IsGenericType);
Assert.True (type.GetGenericTypeDefinition () == typeof (ConsoleDriverFacade<>));
v2.Shutdown ();
Assert.Null (Application.Driver);
netInput.VerifyAll ();
}
private void SetupRunInputMockMethodToBlock (Mock<IWindowsInput> winInput)
{
winInput.Setup (r => r.Run (It.IsAny<CancellationToken> ()))
.Callback<CancellationToken> (token =>
{
// Simulate an infinite loop that checks for cancellation
while (!token.IsCancellationRequested)
{
// Perform the action that should repeat in the loop
// This could be some mock behavior or just an empty loop depending on the context
}
})
.Verifiable (Times.Once);
}
private void SetupRunInputMockMethodToBlock (Mock<INetInput> netInput)
{
netInput.Setup (r => r.Run (It.IsAny<CancellationToken> ()))
.Callback<CancellationToken> (token =>
{
// Simulate an infinite loop that checks for cancellation
while (!token.IsCancellationRequested)
{
// Perform the action that should repeat in the loop
// This could be some mock behavior or just an empty loop depending on the context
}
})
.Verifiable (Times.Once);
}
[Fact]
public void Test_NoInitThrowOnRun ()
{
var app = NewApplicationV2();
var ex = Assert.Throws<NotInitializedException> (() => app.Run (new Window ()));
Assert.Equal ("Run cannot be accessed before Initialization", ex.Message);
}
[Fact]
public void Test_InitRunShutdown ()
{
var orig = ApplicationImpl.Instance;
var v2 = NewApplicationV2();
ApplicationImpl.ChangeInstance (v2);
v2.Init ();
var timeoutToken = v2.AddTimeout (TimeSpan.FromMilliseconds (150),
() =>
{
if (Application.Top != null)
{
Application.RequestStop ();
return true;
}
return true;
}
);
Assert.Null (Application.Top);
// Blocks until the timeout call is hit
v2.Run (new Window ());
Assert.True(v2.RemoveTimeout (timeoutToken));
Assert.Null (Application.Top);
v2.Shutdown ();
ApplicationImpl.ChangeInstance (orig);
}
[Fact]
public void Test_InitRunShutdown_Generic_IdleForExit ()
{
var orig = ApplicationImpl.Instance;
var v2 = NewApplicationV2 ();
ApplicationImpl.ChangeInstance (v2);
v2.Init ();
v2.AddIdle (IdleExit);
Assert.Null (Application.Top);
// Blocks until the timeout call is hit
v2.Run<Window> ();
Assert.Null (Application.Top);
v2.Shutdown ();
ApplicationImpl.ChangeInstance (orig);
}
private bool IdleExit ()
{
if (Application.Top != null)
{
Application.RequestStop ();
return true;
}
return true;
}
[Fact]
public void TestRepeatedShutdownCalls_DoNotDuplicateDisposeOutput ()
{
var netInput = new Mock<INetInput> ();
SetupRunInputMockMethodToBlock (netInput);
Mock<IConsoleOutput>? outputMock = null;
var v2 = new ApplicationV2(
() => netInput.Object,
()=> (outputMock = new Mock<IConsoleOutput>()).Object,
Mock.Of<IWindowsInput>,
Mock.Of<IConsoleOutput>);
v2.Init (null,"v2net");
v2.Shutdown ();
v2.Shutdown ();
outputMock.Verify(o=>o.Dispose (),Times.Once);
}
[Fact]
public void TestRepeatedInitCalls_WarnsAndIgnores ()
{
var v2 = NewApplicationV2 ();
Assert.Null (Application.Driver);
v2.Init ();
Assert.NotNull (Application.Driver);
var mockLogger = new Mock<ILogger> ();
var beforeLogger = Logging.Logger;
Logging.Logger = mockLogger.Object;
v2.Init ();
v2.Init ();
mockLogger.Verify(
l=>l.Log (LogLevel.Error,
It.IsAny<EventId> (),
It.Is<It.IsAnyType> ((v, t) => v.ToString () == "Init called multiple times without shutdown, ignoring."),
It.IsAny<Exception> (),
It.IsAny<Func<It.IsAnyType, Exception, string>> ())
,Times.Exactly (2));
v2.Shutdown ();
// Restore the original null logger to be polite to other tests
Logging.Logger = beforeLogger;
}
}

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace UnitTests.ConsoleDrivers.V2;
public class ConsoleInputTests
{
class FakeInput : ConsoleInput<char>
{
private readonly string [] _reads;
public FakeInput (params string [] reads ) { _reads = reads; }
int iteration = 0;
/// <inheritdoc />
protected override bool Peek ()
{
return iteration < _reads.Length;
}
/// <inheritdoc />
protected override IEnumerable<char> Read ()
{
return _reads [iteration++];
}
}
[Fact]
public void Test_ThrowsIfNotInitialized ()
{
var input = new FakeInput ("Fish");
var ex = Assert.Throws<Exception>(()=>input.Run (new (canceled: true)));
Assert.Equal ("Cannot run input before Initialization", ex.Message);
}
[Fact]
public void Test_Simple ()
{
var input = new FakeInput ("Fish");
var queue = new ConcurrentQueue<char> ();
input.Initialize (queue);
var cts = new CancellationTokenSource ();
cts.CancelAfter (25); // Cancel after 25 milliseconds
CancellationToken token = cts.Token;
Assert.Empty (queue);
input.Run (token);
Assert.Equal ("Fish",new string (queue.ToArray ()));
}
}

View File

@@ -0,0 +1,91 @@
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Moq;
namespace UnitTests.ConsoleDrivers.V2;
public class MainLoopCoordinatorTests
{
[Fact]
public void TestMainLoopCoordinator_InputCrashes_ExceptionSurfacesMainThread ()
{
var mockLogger = new Mock<ILogger> ();
var beforeLogger = Logging.Logger;
Logging.Logger = mockLogger.Object;
var c = new MainLoopCoordinator<char> (new TimedEvents (),
// Runs on a separate thread (input thread)
() => throw new Exception ("Crash on boot"),
// Rest runs on main thread
new ConcurrentQueue<char> (),
Mock.Of <IInputProcessor>(),
()=>Mock.Of<IConsoleOutput>(),
Mock.Of<IMainLoop<char>>());
// StartAsync boots the main loop and the input thread. But if the input class bombs
// on startup it is important that the exception surface at the call site and not lost
var ex = Assert.ThrowsAsync<AggregateException>(c.StartAsync).Result;
Assert.Equal ("Crash on boot", ex.InnerExceptions [0].Message);
// Restore the original null logger to be polite to other tests
Logging.Logger = beforeLogger;
// Logs should explicitly call out that input loop crashed.
mockLogger.Verify (
l => l.Log (LogLevel.Critical,
It.IsAny<EventId> (),
It.Is<It.IsAnyType> ((v, t) => v.ToString () == "Input loop crashed"),
It.IsAny<Exception> (),
It.IsAny<Func<It.IsAnyType, Exception, string>> ())
, Times.Once);
}
/*
[Fact]
public void TestMainLoopCoordinator_InputExitsImmediately_ExceptionRaisedInMainThread ()
{
// Runs on a separate thread (input thread)
// But because it's just a mock it immediately exists
var mockInputFactoryMethod = () => Mock.Of<IConsoleInput<char>> ();
var mockOutput = Mock.Of<IConsoleOutput> ();
var mockInputProcessor = Mock.Of<IInputProcessor> ();
var inputQueue = new ConcurrentQueue<char> ();
var timedEvents = new TimedEvents ();
var mainLoop = new MainLoop<char> ();
mainLoop.Initialize (timedEvents,
inputQueue,
mockInputProcessor,
mockOutput
);
var c = new MainLoopCoordinator<char> (timedEvents,
mockInputFactoryMethod,
inputQueue,
mockInputProcessor,
()=>mockOutput,
mainLoop
);
// TODO: This test has race condition
//
// * When the input loop exits it can happen
// * - During boot
// * - After boot
// *
// * If it happens in boot you get input exited
// * If it happens after you get "Input loop exited early (stop not called)"
//
// Because the console input class does not block - i.e. breaks contract
// We need to let the user know input has silently exited and all has gone bad.
var ex = Assert.ThrowsAsync<Exception> (c.StartAsync).Result;
Assert.Equal ("Input loop exited during startup instead of entering read loop properly (i.e. and blocking)", ex.Message);
}*/
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using Moq;
namespace UnitTests.ConsoleDrivers.V2;
public class MainLoopTTests
{
[Fact]
public void MainLoopT_NotInitialized_Throws()
{
var m = new MainLoop<int> ();
Assert.Throws<NotInitializedException> (() => m.TimedEvents);
Assert.Throws<NotInitializedException> (() => m.InputBuffer);
Assert.Throws<NotInitializedException> (() => m.InputProcessor);
Assert.Throws<NotInitializedException> (() => m.Out);
Assert.Throws<NotInitializedException> (() => m.AnsiRequestScheduler);
Assert.Throws<NotInitializedException> (() => m.WindowSizeMonitor);
m.Initialize (new TimedEvents (),
new ConcurrentQueue<int> (),
Mock.Of <IInputProcessor>(),
Mock.Of<IConsoleOutput>());
Assert.NotNull (m.TimedEvents);
Assert.NotNull (m.InputBuffer);
Assert.NotNull (m.InputProcessor);
Assert.NotNull (m.Out);
Assert.NotNull (m.AnsiRequestScheduler);
Assert.NotNull (m.WindowSizeMonitor);
}
}

View File

@@ -0,0 +1,155 @@
using Moq;
namespace UnitTests.ConsoleDrivers.V2;
public class MouseInterpreterTests
{
[Theory]
[MemberData (nameof (SequenceTests))]
public void TestMouseEventSequences_InterpretedOnlyAsFlag (List<MouseEventArgs> events, params MouseFlags?[] expected)
{
// Arrange: Mock dependencies and set up the interpreter
var interpreter = new MouseInterpreter (null);
// Act and Assert
for (int i = 0; i < events.Count; i++)
{
var results = interpreter.Process (events [i]).ToArray();
// Raw input event should be there
Assert.Equal (events [i].Flags, results [0].Flags);
// also any expected should be there
if (expected [i] != null)
{
Assert.Equal (expected [i], results [1].Flags);
}
else
{
Assert.Single (results);
}
}
}
public static IEnumerable<object []> SequenceTests ()
{
yield return new object []
{
new List<MouseEventArgs>
{
new() { Flags = MouseFlags.Button1Pressed },
new()
},
null,
MouseFlags.Button1Clicked
};
yield return new object []
{
new List<MouseEventArgs>
{
new() { Flags = MouseFlags.Button1Pressed },
new(),
new() { Flags = MouseFlags.Button1Pressed },
new()
},
null,
MouseFlags.Button1Clicked,
null,
MouseFlags.Button1DoubleClicked
};
yield return new object []
{
new List<MouseEventArgs>
{
new() { Flags = MouseFlags.Button1Pressed },
new(),
new() { Flags = MouseFlags.Button1Pressed },
new(),
new() { Flags = MouseFlags.Button1Pressed },
new()
},
null,
MouseFlags.Button1Clicked,
null,
MouseFlags.Button1DoubleClicked,
null,
MouseFlags.Button1TripleClicked
};
yield return new object []
{
new List<MouseEventArgs>
{
new() { Flags = MouseFlags.Button2Pressed },
new(),
new() { Flags = MouseFlags.Button2Pressed },
new(),
new() { Flags = MouseFlags.Button2Pressed },
new()
},
null,
MouseFlags.Button2Clicked,
null,
MouseFlags.Button2DoubleClicked,
null,
MouseFlags.Button2TripleClicked
};
yield return new object []
{
new List<MouseEventArgs>
{
new() { Flags = MouseFlags.Button3Pressed },
new(),
new() { Flags = MouseFlags.Button3Pressed },
new(),
new() { Flags = MouseFlags.Button3Pressed },
new()
},
null,
MouseFlags.Button3Clicked,
null,
MouseFlags.Button3DoubleClicked,
null,
MouseFlags.Button3TripleClicked
};
yield return new object []
{
new List<MouseEventArgs>
{
new() { Flags = MouseFlags.Button4Pressed },
new(),
new() { Flags = MouseFlags.Button4Pressed },
new(),
new() { Flags = MouseFlags.Button4Pressed },
new()
},
null,
MouseFlags.Button4Clicked,
null,
MouseFlags.Button4DoubleClicked,
null,
MouseFlags.Button4TripleClicked
};
yield return new object []
{
new List<MouseEventArgs>
{
new() { Flags = MouseFlags.Button1Pressed ,Position = new Point (10,11)},
new(){Position = new Point (10,11)},
// Clicking the line below means no double click because it's a different location
new() { Flags = MouseFlags.Button1Pressed,Position = new Point (10,12) },
new(){Position = new Point (10,12)}
},
null,
MouseFlags.Button1Clicked,
null,
MouseFlags.Button1Clicked //release is click because new position
};
}
}

View File

@@ -0,0 +1,120 @@
using System.Collections.Concurrent;
using System.Text;
namespace UnitTests.ConsoleDrivers.V2;
public class NetInputProcessorTests
{
public static IEnumerable<object []> GetConsoleKeyInfoToKeyTestCases_Rune ()
{
yield return new object [] { new ConsoleKeyInfo ('C', ConsoleKey.None, false, false, false), new Rune('C') };
yield return new object [] { new ConsoleKeyInfo ('\\', ConsoleKey.Oem5, false, false, false), new Rune ('\\') };
yield return new object [] { new ConsoleKeyInfo ('+', ConsoleKey.OemPlus, true, false, false), new Rune ('+') };
yield return new object [] { new ConsoleKeyInfo ('=', ConsoleKey.OemPlus, false, false, false), new Rune ('=') };
yield return new object [] { new ConsoleKeyInfo ('_', ConsoleKey.OemMinus, true, false, false), new Rune ('_') };
yield return new object [] { new ConsoleKeyInfo ('-', ConsoleKey.OemMinus, false, false, false), new Rune ('-') };
yield return new object [] { new ConsoleKeyInfo (')', ConsoleKey.None, false, false, false), new Rune (')') };
yield return new object [] { new ConsoleKeyInfo ('0', ConsoleKey.None, false, false, false), new Rune ('0') };
yield return new object [] { new ConsoleKeyInfo ('(', ConsoleKey.None, false, false, false), new Rune ('(') };
yield return new object [] { new ConsoleKeyInfo ('9', ConsoleKey.None, false, false, false), new Rune ('9') };
yield return new object [] { new ConsoleKeyInfo ('*', ConsoleKey.None, false, false, false), new Rune ('*') };
yield return new object [] { new ConsoleKeyInfo ('8', ConsoleKey.None, false, false, false), new Rune ('8') };
yield return new object [] { new ConsoleKeyInfo ('&', ConsoleKey.None, false, false, false), new Rune ('&') };
yield return new object [] { new ConsoleKeyInfo ('7', ConsoleKey.None, false, false, false), new Rune ('7') };
yield return new object [] { new ConsoleKeyInfo ('^', ConsoleKey.None, false, false, false), new Rune ('^') };
yield return new object [] { new ConsoleKeyInfo ('6', ConsoleKey.None, false, false, false), new Rune ('6') };
yield return new object [] { new ConsoleKeyInfo ('%', ConsoleKey.None, false, false, false), new Rune ('%') };
yield return new object [] { new ConsoleKeyInfo ('5', ConsoleKey.None, false, false, false), new Rune ('5') };
yield return new object [] { new ConsoleKeyInfo ('$', ConsoleKey.None, false, false, false), new Rune ('$') };
yield return new object [] { new ConsoleKeyInfo ('4', ConsoleKey.None, false, false, false), new Rune ('4') };
yield return new object [] { new ConsoleKeyInfo ('#', ConsoleKey.None, false, false, false), new Rune ('#') };
yield return new object [] { new ConsoleKeyInfo ('@', ConsoleKey.None, false, false, false), new Rune ('@') };
yield return new object [] { new ConsoleKeyInfo ('2', ConsoleKey.None, false, false, false), new Rune ('2') };
yield return new object [] { new ConsoleKeyInfo ('!', ConsoleKey.None, false, false, false), new Rune ('!') };
yield return new object [] { new ConsoleKeyInfo ('1', ConsoleKey.None, false, false, false), new Rune ('1') };
yield return new object [] { new ConsoleKeyInfo ('\t', ConsoleKey.None, false, false, false), new Rune ('\t') };
yield return new object [] { new ConsoleKeyInfo ('}', ConsoleKey.Oem6, true, false, false), new Rune ('}') };
yield return new object [] { new ConsoleKeyInfo (']', ConsoleKey.Oem6, false, false, false), new Rune (']') };
yield return new object [] { new ConsoleKeyInfo ('{', ConsoleKey.Oem4, true, false, false), new Rune ('{') };
yield return new object [] { new ConsoleKeyInfo ('[', ConsoleKey.Oem4, false, false, false), new Rune ('[') };
yield return new object [] { new ConsoleKeyInfo ('\"', ConsoleKey.Oem7, true, false, false), new Rune ('\"') };
yield return new object [] { new ConsoleKeyInfo ('\'', ConsoleKey.Oem7, false, false, false), new Rune ('\'') };
yield return new object [] { new ConsoleKeyInfo (':', ConsoleKey.Oem1, true, false, false), new Rune (':') };
yield return new object [] { new ConsoleKeyInfo (';', ConsoleKey.Oem1, false, false, false), new Rune (';') };
yield return new object [] { new ConsoleKeyInfo ('?', ConsoleKey.Oem2, true, false, false), new Rune ('?') };
yield return new object [] { new ConsoleKeyInfo ('/', ConsoleKey.Oem2, false, false, false), new Rune ('/') };
yield return new object [] { new ConsoleKeyInfo ('>', ConsoleKey.OemPeriod, true, false, false), new Rune ('>') };
yield return new object [] { new ConsoleKeyInfo ('.', ConsoleKey.OemPeriod, false, false, false), new Rune ('.') };
yield return new object [] { new ConsoleKeyInfo ('<', ConsoleKey.OemComma, true, false, false), new Rune ('<') };
yield return new object [] { new ConsoleKeyInfo (',', ConsoleKey.OemComma, false, false, false), new Rune (',') };
yield return new object [] { new ConsoleKeyInfo ('w', ConsoleKey.None, false, false, false), new Rune ('w') };
yield return new object [] { new ConsoleKeyInfo ('e', ConsoleKey.None, false, false, false), new Rune ('e') };
yield return new object [] { new ConsoleKeyInfo ('a', ConsoleKey.None, false, false, false), new Rune ('a') };
yield return new object [] { new ConsoleKeyInfo ('s', ConsoleKey.None, false, false, false), new Rune ('s') };
}
[Theory]
[MemberData (nameof (GetConsoleKeyInfoToKeyTestCases_Rune))]
public void ConsoleKeyInfoToKey_ValidInput_AsRune (ConsoleKeyInfo input, Rune expected)
{
var converter = new NetKeyConverter ();
// Act
var result = converter.ToKey (input);
// Assert
Assert.Equal (expected, result.AsRune);
}
public static IEnumerable<object []> GetConsoleKeyInfoToKeyTestCases_Key ()
{
yield return new object [] { new ConsoleKeyInfo ('\t', ConsoleKey.None, false, false, false), Key.Tab};
yield return new object [] { new ConsoleKeyInfo ('\u001B', ConsoleKey.None, false, false, false), Key.Esc };
yield return new object [] { new ConsoleKeyInfo ('\u007f', ConsoleKey.None, false, false, false), Key.Backspace };
// TODO: Terminal.Gui does not have a Key for this mapped
// TODO: null and default(Key) are both not same as Null. Why user has to do (Key)0 to get a null key?!
yield return new object [] { new ConsoleKeyInfo ('\0', ConsoleKey.LeftWindows, false, false, false), (Key)0 };
}
[Theory]
[MemberData (nameof (GetConsoleKeyInfoToKeyTestCases_Key))]
public void ConsoleKeyInfoToKey_ValidInput_AsKey (ConsoleKeyInfo input, Key expected)
{
var converter = new NetKeyConverter ();
// Act
var result = converter.ToKey (input);
// Assert
Assert.Equal (expected, result);
}
[Fact]
public void Test_ProcessQueue_CapitalHLowerE ()
{
var queue = new ConcurrentQueue<ConsoleKeyInfo> ();
queue.Enqueue (new ConsoleKeyInfo ('H', ConsoleKey.None, true, false, false));
queue.Enqueue (new ConsoleKeyInfo ('e', ConsoleKey.None, false, false, false));
var processor = new NetInputProcessor (queue);
List<Key> ups = new List<Key> ();
List<Key> downs = new List<Key> ();
processor.KeyUp += (s, e) => { ups.Add (e); };
processor.KeyDown += (s, e) => { downs.Add (e); };
Assert.Empty (ups);
Assert.Empty (downs);
processor.ProcessQueue ();
Assert.Equal (Key.H.WithShift, ups [0]);
Assert.Equal (Key.H.WithShift, downs [0]);
Assert.Equal (Key.E, ups [1]);
Assert.Equal (Key.E, downs [1]);
}
}

View File

@@ -0,0 +1,76 @@
using Moq;
namespace UnitTests.ConsoleDrivers.V2;
public class WindowSizeMonitorTests
{
[Fact]
public void TestWindowSizeMonitor_RaisesEventWhenChanges ()
{
var consoleOutput = new Mock<IConsoleOutput> ();
var queue = new Queue<Size>(new []{
new Size (30, 20),
new Size (20, 20)
});
consoleOutput.Setup (m => m.GetWindowSize ())
.Returns (queue.Dequeue);
var outputBuffer = Mock.Of<IOutputBuffer> ();
var monitor = new WindowSizeMonitor (consoleOutput.Object, outputBuffer);
var result = new List<SizeChangedEventArgs> ();
monitor.SizeChanging += (s, e) => { result.Add (e);};
Assert.Empty (result);
monitor.Poll ();
Assert.Single (result);
Assert.Equal (new Size (30,20),result [0].Size);
monitor.Poll ();
Assert.Equal (2,result.Count);
Assert.Equal (new Size (30, 20), result [0].Size);
Assert.Equal (new Size (20, 20), result [1].Size);
}
[Fact]
public void TestWindowSizeMonitor_DoesNotRaiseEventWhen_NoChanges ()
{
var consoleOutput = new Mock<IConsoleOutput> ();
var queue = new Queue<Size> (new []{
new Size (30, 20),
new Size (30, 20),
});
consoleOutput.Setup (m => m.GetWindowSize ())
.Returns (queue.Dequeue);
var outputBuffer = Mock.Of<IOutputBuffer> ();
var monitor = new WindowSizeMonitor (consoleOutput.Object, outputBuffer);
var result = new List<SizeChangedEventArgs> ();
monitor.SizeChanging += (s, e) => { result.Add (e); };
// First poll always raises event because going from unknown size i.e. 0,0
Assert.Empty (result);
monitor.Poll ();
Assert.Single (result);
Assert.Equal (new Size (30, 20), result [0].Size);
// No change
monitor.Poll ();
Assert.Single (result);
Assert.Equal (new Size (30, 20), result [0].Size);
}
}

View File

@@ -0,0 +1,305 @@
using System.Collections.Concurrent;
using Terminal.Gui.ConsoleDrivers;
using InputRecord = Terminal.Gui.WindowsConsole.InputRecord;
using ButtonState = Terminal.Gui.WindowsConsole.ButtonState;
using MouseEventRecord = Terminal.Gui.WindowsConsole.MouseEventRecord;
namespace UnitTests.ConsoleDrivers.V2;
public class WindowsInputProcessorTests
{
[Fact]
public void Test_ProcessQueue_CapitalHLowerE ()
{
var queue = new ConcurrentQueue<InputRecord> ();
queue.Enqueue (new InputRecord()
{
EventType = WindowsConsole.EventType.Key,
KeyEvent = new WindowsConsole.KeyEventRecord ()
{
bKeyDown = true,
UnicodeChar = 'H',
dwControlKeyState = WindowsConsole.ControlKeyState.CapslockOn,
wVirtualKeyCode = (ConsoleKeyMapping.VK)72,
wVirtualScanCode = 35
}
});
queue.Enqueue (new InputRecord ()
{
EventType = WindowsConsole.EventType.Key,
KeyEvent = new WindowsConsole.KeyEventRecord ()
{
bKeyDown = false,
UnicodeChar = 'H',
dwControlKeyState = WindowsConsole.ControlKeyState.CapslockOn,
wVirtualKeyCode = (ConsoleKeyMapping.VK)72,
wVirtualScanCode = 35
}
});
queue.Enqueue (new InputRecord ()
{
EventType = WindowsConsole.EventType.Key,
KeyEvent = new WindowsConsole.KeyEventRecord ()
{
bKeyDown = true,
UnicodeChar = 'i',
dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
wVirtualKeyCode = (ConsoleKeyMapping.VK)73,
wVirtualScanCode = 23
}
});
queue.Enqueue (new InputRecord ()
{
EventType = WindowsConsole.EventType.Key,
KeyEvent = new WindowsConsole.KeyEventRecord ()
{
bKeyDown = false,
UnicodeChar = 'i',
dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
wVirtualKeyCode = (ConsoleKeyMapping.VK)73,
wVirtualScanCode = 23
}
});
var processor = new WindowsInputProcessor (queue);
List<Key> ups = new List<Key> ();
List<Key> downs = new List<Key> ();
processor.KeyUp += (s, e) => { ups.Add (e); };
processor.KeyDown += (s, e) => { downs.Add (e); };
Assert.Empty (ups);
Assert.Empty (downs);
processor.ProcessQueue ();
Assert.Equal (Key.H.WithShift, ups [0]);
Assert.Equal (Key.H.WithShift, downs [0]);
Assert.Equal (Key.I, ups [1]);
Assert.Equal (Key.I, downs [1]);
}
[Fact]
public void Test_ProcessQueue_Mouse_Move ()
{
var queue = new ConcurrentQueue<InputRecord> ();
queue.Enqueue (new InputRecord ()
{
EventType = WindowsConsole.EventType.Mouse,
MouseEvent = new WindowsConsole.MouseEventRecord
{
MousePosition = new WindowsConsole.Coord(32,31),
ButtonState = WindowsConsole.ButtonState.NoButtonPressed,
ControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
EventFlags = WindowsConsole.EventFlags.MouseMoved
}
});
var processor = new WindowsInputProcessor (queue);
List<MouseEventArgs> mouseEvents = new List<MouseEventArgs> ();
processor.MouseEvent += (s, e) => { mouseEvents.Add (e); };
Assert.Empty (mouseEvents);
processor.ProcessQueue ();
var s = Assert.Single (mouseEvents);
Assert.Equal (s.Flags,MouseFlags.ReportMousePosition);
Assert.Equal (s.ScreenPosition,new Point (32,31));
}
[Theory]
[InlineData(WindowsConsole.ButtonState.Button1Pressed,MouseFlags.Button1Pressed)]
[InlineData (WindowsConsole.ButtonState.Button2Pressed, MouseFlags.Button2Pressed)]
[InlineData (WindowsConsole.ButtonState.Button3Pressed, MouseFlags.Button3Pressed)]
[InlineData (WindowsConsole.ButtonState.Button4Pressed, MouseFlags.Button4Pressed)]
internal void Test_ProcessQueue_Mouse_Pressed (WindowsConsole.ButtonState state,MouseFlags expectedFlag )
{
var queue = new ConcurrentQueue<InputRecord> ();
queue.Enqueue (new InputRecord ()
{
EventType = WindowsConsole.EventType.Mouse,
MouseEvent = new WindowsConsole.MouseEventRecord
{
MousePosition = new WindowsConsole.Coord (32, 31),
ButtonState = state,
ControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
EventFlags = WindowsConsole.EventFlags.MouseMoved
}
});
var processor = new WindowsInputProcessor (queue);
List<MouseEventArgs> mouseEvents = new List<MouseEventArgs> ();
processor.MouseEvent += (s, e) => { mouseEvents.Add (e); };
Assert.Empty (mouseEvents);
processor.ProcessQueue ();
var s = Assert.Single (mouseEvents);
Assert.Equal (s.Flags, MouseFlags.ReportMousePosition | expectedFlag);
Assert.Equal (s.ScreenPosition, new Point (32, 31));
}
[Theory]
[InlineData (100, MouseFlags.WheeledUp)]
[InlineData ( -100, MouseFlags.WheeledDown)]
internal void Test_ProcessQueue_Mouse_Wheel (int wheelValue, MouseFlags expectedFlag)
{
var queue = new ConcurrentQueue<InputRecord> ();
queue.Enqueue (new InputRecord ()
{
EventType = WindowsConsole.EventType.Mouse,
MouseEvent = new WindowsConsole.MouseEventRecord
{
MousePosition = new WindowsConsole.Coord (32, 31),
ButtonState = (WindowsConsole.ButtonState)wheelValue,
ControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
EventFlags = WindowsConsole.EventFlags.MouseWheeled
}
});
var processor = new WindowsInputProcessor (queue);
List<MouseEventArgs> mouseEvents = new List<MouseEventArgs> ();
processor.MouseEvent += (s, e) => { mouseEvents.Add (e); };
Assert.Empty (mouseEvents);
processor.ProcessQueue ();
var s = Assert.Single (mouseEvents);
Assert.Equal (s.Flags,expectedFlag);
Assert.Equal (s.ScreenPosition, new Point (32, 31));
}
public static IEnumerable<object []> MouseFlagTestData ()
{
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
{
Tuple.Create(ButtonState.Button1Pressed, MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button1Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
}
};
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
{
Tuple.Create ( ButtonState.Button2Pressed, MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button2Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
}
};
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
{
Tuple.Create(ButtonState.Button3Pressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button3Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
}
};
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
{
Tuple.Create(ButtonState.Button4Pressed, MouseFlags.Button4Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button4Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
}
};
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
{
Tuple.Create(ButtonState.RightmostButtonPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button3Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
}
};
// Tests for holding down 2 buttons at once and releasing them one after the other
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
{
Tuple.Create(ButtonState.Button1Pressed | ButtonState.Button2Pressed, MouseFlags.Button1Pressed | MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.Button1Pressed, MouseFlags.Button1Pressed | MouseFlags.Button2Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button1Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
}
};
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
{
Tuple.Create(ButtonState.Button3Pressed | ButtonState.Button4Pressed, MouseFlags.Button3Pressed | MouseFlags.Button4Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.Button3Pressed, MouseFlags.Button3Pressed | MouseFlags.Button4Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button3Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
}
};
// Test for holding down 2 buttons at once and releasing them simultaneously
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
{
Tuple.Create(ButtonState.Button1Pressed | ButtonState.Button2Pressed, MouseFlags.Button1Pressed | MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button1Released | MouseFlags.Button2Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
}
};
// Test that rightmost and button 3 are the same button so 2 states is still only 1 flag
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
{
Tuple.Create(ButtonState.Button3Pressed | ButtonState.RightmostButtonPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
// Can swap between without raising the released
Tuple.Create(ButtonState.Button3Pressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.RightmostButtonPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
// Now with neither we get released
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button3Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
}
};
}
[Theory]
[MemberData (nameof (MouseFlagTestData))]
internal void MouseFlags_Should_Map_Correctly (Tuple<ButtonState, MouseFlags>[] inputOutputPairs)
{
var processor = new WindowsInputProcessor (new ());
foreach (var pair in inputOutputPairs)
{
var mockEvent = new MouseEventRecord { ButtonState = pair.Item1 };
var result = processor.ToDriverMouse (mockEvent);
Assert.Equal (pair.Item2, result.Flags);
}
}
}

View File

@@ -222,15 +222,20 @@ public class ScenarioTests : TestsAllViews
initialized = true;
Application.Iteration += OnApplicationOnIteration;
Application.Driver!.ClearedContents += (sender, args) => clearedContentCount++;
Application.Driver!.Refreshed += (sender, args) =>
{
refreshedCount++;
if (args.CurrentValue)
{
updatedCount++;
}
};
if (Application.Driver is ConsoleDriver cd)
{
cd!.Refreshed += (sender, args) =>
{
refreshedCount++;
if (args.CurrentValue)
{
updatedCount++;
}
};
}
Application.NotifyNewRunState += OnApplicationNotifyNewRunState;
stopwatch = Stopwatch.StartNew ();
@@ -800,7 +805,7 @@ public class ScenarioTests : TestsAllViews
if (token == null)
{
// Timeout only must start at first iteration
token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (ms), abortCallback);
token = Application.AddTimeout (TimeSpan.FromMilliseconds (ms), abortCallback);
}
iterations++;

View File

@@ -5,10 +5,13 @@ namespace Terminal.Gui.LayoutTests;
public class AllViewsDrawTests (ITestOutputHelper _output) : TestsAllViews
{
[Theory]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
[MemberData (nameof (AllViewTypes))]
public void AllViews_Draw_Does_Not_Layout (Type viewType)
{
Application.ResetState (true);
// Required for spinner view that wants to register timeouts
Application.MainLoop = new MainLoop (new FakeMainLoop (Application.Driver));
var view = (View)CreateInstanceIfNotGeneric (viewType);

View File

@@ -11,6 +11,7 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
/// events: KeyDown and KeyDownNotHandled. Note that KeyUp is independent.
/// </summary>
[Theory]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
[MemberData (nameof (AllViewTypes))]
public void AllViews_NewKeyDownEvent_All_EventsFire (Type viewType)
{
@@ -53,6 +54,7 @@ public class KeyboardEventTests (ITestOutputHelper output) : TestsAllViews
/// KeyUp
/// </summary>
[Theory]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
[MemberData (nameof (AllViewTypes))]
public void AllViews_NewKeyUpEvent_All_EventsFire (Type viewType)
{

View File

@@ -5,9 +5,14 @@ namespace Terminal.Gui.LayoutTests;
public class LayoutTests (ITestOutputHelper _output) : TestsAllViews
{
[Theory]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
[MemberData (nameof (AllViewTypes))]
public void AllViews_Layout_Does_Not_Draw (Type viewType)
{
// Required for spinner view that wants to register timeouts
Application.MainLoop = new MainLoop (new FakeMainLoop (Application.Driver));
var view = (View)CreateInstanceIfNotGeneric (viewType);
if (view == null)

View File

@@ -154,6 +154,7 @@ public class MouseTests (ITestOutputHelper output) : TestsAllViews
}
[Theory]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
[MemberData (nameof (AllViewTypes))]
public void AllViews_NewMouseEvent_Enabled_False_Does_Not_Set_Handled (Type viewType)
{
@@ -173,6 +174,7 @@ public class MouseTests (ITestOutputHelper output) : TestsAllViews
}
[Theory]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
[MemberData (nameof (AllViewTypes))]
public void AllViews_NewMouseEvent_Clicked_Enabled_False_Does_Not_Set_Handled (Type viewType)
{

View File

@@ -12,8 +12,12 @@ public class AllViewsTests (ITestOutputHelper output) : TestsAllViews
[Theory]
[MemberData (nameof (AllViewTypes))]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
public void AllViews_Center_Properly (Type viewType)
{
// Required for spinner view that wants to register timeouts
Application.MainLoop = new MainLoop (new FakeMainLoop (Application.Driver));
var view = (View)CreateInstanceIfNotGeneric (viewType);
// See https://github.com/gui-cs/Terminal.Gui/issues/3156
@@ -62,6 +66,7 @@ public class AllViewsTests (ITestOutputHelper output) : TestsAllViews
[Theory]
[MemberData (nameof (AllViewTypes))]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
public void AllViews_Tests_All_Constructors (Type viewType)
{
Assert.True (Test_All_Constructors_Of_Type (viewType));
@@ -97,8 +102,12 @@ public class AllViewsTests (ITestOutputHelper output) : TestsAllViews
[Theory]
[MemberData (nameof (AllViewTypes))]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
public void AllViews_Command_Select_Raises_Selecting (Type viewType)
{
// Required for spinner view that wants to register timeouts
Application.MainLoop = new MainLoop (new FakeMainLoop (Application.Driver));
var view = (View)CreateInstanceIfNotGeneric (viewType);
if (view == null)
@@ -131,9 +140,13 @@ public class AllViewsTests (ITestOutputHelper output) : TestsAllViews
}
[Theory]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
[MemberData (nameof (AllViewTypes))]
public void AllViews_Command_Accept_Raises_Accepted (Type viewType)
{
// Required for spinner view that wants to register timeouts
Application.MainLoop = new MainLoop (new FakeMainLoop (Application.Driver));
var view = (View)CreateInstanceIfNotGeneric (viewType);
if (view == null)
@@ -168,8 +181,12 @@ public class AllViewsTests (ITestOutputHelper output) : TestsAllViews
[Theory]
[MemberData (nameof (AllViewTypes))]
[SetupFakeDriver] // Required for spinner view that wants to register timeouts
public void AllViews_Command_HotKey_Raises_HandlingHotKey (Type viewType)
{
// Required for spinner view that wants to register timeouts
Application.MainLoop = new MainLoop (new FakeMainLoop (Application.Driver));
var view = (View)CreateInstanceIfNotGeneric (viewType);
if (view == null)

View File

@@ -15,33 +15,33 @@ public class SpinnerViewTests
{
SpinnerView view = GetSpinnerView ();
Assert.Empty (Application.MainLoop._timeouts);
Assert.Empty (Application.MainLoop.TimedEvents.Timeouts);
view.AutoSpin = true;
Assert.NotEmpty (Application.MainLoop._timeouts);
Assert.NotEmpty (Application.MainLoop.TimedEvents.Timeouts);
Assert.True (view.AutoSpin);
//More calls to AutoSpin do not add more timeouts
Assert.Single (Application.MainLoop._timeouts);
Assert.Single (Application.MainLoop.TimedEvents.Timeouts);
view.AutoSpin = true;
view.AutoSpin = true;
view.AutoSpin = true;
Assert.True (view.AutoSpin);
Assert.Single (Application.MainLoop._timeouts);
Assert.Single (Application.MainLoop.TimedEvents.Timeouts);
if (callStop)
{
view.AutoSpin = false;
Assert.Empty (Application.MainLoop._timeouts);
Assert.Empty (Application.MainLoop.TimedEvents.Timeouts);
Assert.False (view.AutoSpin);
}
else
{
Assert.NotEmpty (Application.MainLoop._timeouts);
Assert.NotEmpty (Application.MainLoop.TimedEvents.Timeouts);
}
// Dispose clears timeout
view.Dispose ();
Assert.Empty (Application.MainLoop._timeouts);
Assert.Empty (Application.MainLoop.TimedEvents.Timeouts);
Application.Top.Dispose ();
}