mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2026-01-02 01:03:29 +01:00
Fixes #3692++ - Rearchitects drivers (#3837)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
73
UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs
Normal file
73
UnitTests/ConsoleDrivers/AnsiKeyboardParserTests.cs
Normal 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
|
||||
}
|
||||
}
|
||||
42
UnitTests/ConsoleDrivers/AnsiMouseParserTests.cs
Normal file
42
UnitTests/ConsoleDrivers/AnsiMouseParserTests.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 ();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 ();
|
||||
|
||||
287
UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
Normal file
287
UnitTests/ConsoleDrivers/V2/ApplicationV2Tests.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
57
UnitTests/ConsoleDrivers/V2/ConsoleInputTests.cs
Normal file
57
UnitTests/ConsoleDrivers/V2/ConsoleInputTests.cs
Normal 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 ()));
|
||||
}
|
||||
}
|
||||
91
UnitTests/ConsoleDrivers/V2/MainLoopCoordinatorTests.cs
Normal file
91
UnitTests/ConsoleDrivers/V2/MainLoopCoordinatorTests.cs
Normal 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);
|
||||
}*/
|
||||
}
|
||||
33
UnitTests/ConsoleDrivers/V2/MainLoopTTests.cs
Normal file
33
UnitTests/ConsoleDrivers/V2/MainLoopTTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
155
UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
Normal file
155
UnitTests/ConsoleDrivers/V2/MouseInterpreterTests.cs
Normal 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
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
120
UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs
Normal file
120
UnitTests/ConsoleDrivers/V2/NetInputProcessorTests.cs
Normal 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]);
|
||||
}
|
||||
}
|
||||
76
UnitTests/ConsoleDrivers/V2/WindowSizeMonitorTests.cs
Normal file
76
UnitTests/ConsoleDrivers/V2/WindowSizeMonitorTests.cs
Normal 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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
305
UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs
Normal file
305
UnitTests/ConsoleDrivers/V2/WindowsInputProcessorTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user