mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-29 01:07:58 +01:00
Merge branch 'v2_develop' into v2_4382_stringextensions-getcolumns-fix
This commit is contained in:
@@ -1,25 +1,27 @@
|
||||
namespace UnitTests_Parallelizable.ApplicationTests;
|
||||
using UnitTests;
|
||||
|
||||
public class StackExtensionsTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
namespace UnitTests_Parallelizable.ApplicationTests;
|
||||
|
||||
public class StackExtensionsTests : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void Stack_Toplevels_Contains ()
|
||||
public void Stack_topLevels_Contains ()
|
||||
{
|
||||
Stack<Toplevel> Toplevels = CreateToplevels ();
|
||||
Stack<Toplevel> topLevels = CreatetopLevels ();
|
||||
var comparer = new ToplevelEqualityComparer ();
|
||||
|
||||
Assert.True (Toplevels.Contains (new Window { Id = "w2" }, comparer));
|
||||
Assert.False (Toplevels.Contains (new Toplevel { Id = "top2" }, comparer));
|
||||
Assert.True (topLevels.Contains (new Window { Id = "w2" }, comparer));
|
||||
Assert.False (topLevels.Contains (new Toplevel { Id = "top2" }, comparer));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Stack_Toplevels_CreateToplevels ()
|
||||
public void Stack_topLevels_CreatetopLevels ()
|
||||
{
|
||||
Stack<Toplevel> Toplevels = CreateToplevels ();
|
||||
Stack<Toplevel> topLevels = CreatetopLevels ();
|
||||
|
||||
int index = Toplevels.Count - 1;
|
||||
int index = topLevels.Count - 1;
|
||||
|
||||
foreach (Toplevel top in Toplevels)
|
||||
foreach (Toplevel top in topLevels)
|
||||
{
|
||||
if (top.GetType () == typeof (Toplevel))
|
||||
{
|
||||
@@ -33,7 +35,7 @@ public class StackExtensionsTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
index--;
|
||||
}
|
||||
|
||||
Toplevel [] tops = Toplevels.ToArray ();
|
||||
Toplevel [] tops = topLevels.ToArray ();
|
||||
|
||||
Assert.Equal ("w4", tops [0].Id);
|
||||
Assert.Equal ("w3", tops [1].Id);
|
||||
@@ -43,28 +45,28 @@ public class StackExtensionsTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Stack_Toplevels_FindDuplicates ()
|
||||
public void Stack_topLevels_FindDuplicates ()
|
||||
{
|
||||
Stack<Toplevel> Toplevels = CreateToplevels ();
|
||||
Stack<Toplevel> topLevels = CreatetopLevels ();
|
||||
var comparer = new ToplevelEqualityComparer ();
|
||||
|
||||
Toplevels.Push (new Toplevel { Id = "w4" });
|
||||
Toplevels.Push (new Toplevel { Id = "w1" });
|
||||
topLevels.Push (new Toplevel { Id = "w4" });
|
||||
topLevels.Push (new Toplevel { Id = "w1" });
|
||||
|
||||
Toplevel [] dup = Toplevels.FindDuplicates (comparer).ToArray ();
|
||||
Toplevel [] dup = topLevels.FindDuplicates (comparer).ToArray ();
|
||||
|
||||
Assert.Equal ("w4", dup [0].Id);
|
||||
Assert.Equal ("w1", dup [^1].Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Stack_Toplevels_MoveNext ()
|
||||
public void Stack_topLevels_MoveNext ()
|
||||
{
|
||||
Stack<Toplevel> Toplevels = CreateToplevels ();
|
||||
Stack<Toplevel> topLevels = CreatetopLevels ();
|
||||
|
||||
Toplevels.MoveNext ();
|
||||
topLevels.MoveNext ();
|
||||
|
||||
Toplevel [] tops = Toplevels.ToArray ();
|
||||
Toplevel [] tops = topLevels.ToArray ();
|
||||
|
||||
Assert.Equal ("w3", tops [0].Id);
|
||||
Assert.Equal ("w2", tops [1].Id);
|
||||
@@ -74,13 +76,13 @@ public class StackExtensionsTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Stack_Toplevels_MovePrevious ()
|
||||
public void Stack_topLevels_MovePrevious ()
|
||||
{
|
||||
Stack<Toplevel> Toplevels = CreateToplevels ();
|
||||
Stack<Toplevel> topLevels = CreatetopLevels ();
|
||||
|
||||
Toplevels.MovePrevious ();
|
||||
topLevels.MovePrevious ();
|
||||
|
||||
Toplevel [] tops = Toplevels.ToArray ();
|
||||
Toplevel [] tops = topLevels.ToArray ();
|
||||
|
||||
Assert.Equal ("Top", tops [0].Id);
|
||||
Assert.Equal ("w4", tops [1].Id);
|
||||
@@ -90,16 +92,16 @@ public class StackExtensionsTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Stack_Toplevels_MoveTo ()
|
||||
public void Stack_topLevels_MoveTo ()
|
||||
{
|
||||
Stack<Toplevel> Toplevels = CreateToplevels ();
|
||||
Stack<Toplevel> topLevels = CreatetopLevels ();
|
||||
|
||||
var valueToMove = new Window { Id = "w1" };
|
||||
var comparer = new ToplevelEqualityComparer ();
|
||||
|
||||
Toplevels.MoveTo (valueToMove, 1, comparer);
|
||||
topLevels.MoveTo (valueToMove, 1, comparer);
|
||||
|
||||
Toplevel [] tops = Toplevels.ToArray ();
|
||||
Toplevel [] tops = topLevels.ToArray ();
|
||||
|
||||
Assert.Equal ("w4", tops [0].Id);
|
||||
Assert.Equal ("w1", tops [1].Id);
|
||||
@@ -109,16 +111,16 @@ public class StackExtensionsTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Stack_Toplevels_MoveTo_From_Last_To_Top ()
|
||||
public void Stack_topLevels_MoveTo_From_Last_To_Top ()
|
||||
{
|
||||
Stack<Toplevel> Toplevels = CreateToplevels ();
|
||||
Stack<Toplevel> topLevels = CreatetopLevels ();
|
||||
|
||||
var valueToMove = new Window { Id = "Top" };
|
||||
var comparer = new ToplevelEqualityComparer ();
|
||||
|
||||
Toplevels.MoveTo (valueToMove, 0, comparer);
|
||||
topLevels.MoveTo (valueToMove, 0, comparer);
|
||||
|
||||
Toplevel [] tops = Toplevels.ToArray ();
|
||||
Toplevel [] tops = topLevels.ToArray ();
|
||||
|
||||
Assert.Equal ("Top", tops [0].Id);
|
||||
Assert.Equal ("w4", tops [1].Id);
|
||||
@@ -128,17 +130,17 @@ public class StackExtensionsTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Stack_Toplevels_Replace ()
|
||||
public void Stack_topLevels_Replace ()
|
||||
{
|
||||
Stack<Toplevel> Toplevels = CreateToplevels ();
|
||||
Stack<Toplevel> topLevels = CreatetopLevels ();
|
||||
|
||||
var valueToReplace = new Window { Id = "w1" };
|
||||
var valueToReplaceWith = new Window { Id = "new" };
|
||||
var comparer = new ToplevelEqualityComparer ();
|
||||
|
||||
Toplevels.Replace (valueToReplace, valueToReplaceWith, comparer);
|
||||
topLevels.Replace (valueToReplace, valueToReplaceWith, comparer);
|
||||
|
||||
Toplevel [] tops = Toplevels.ToArray ();
|
||||
Toplevel [] tops = topLevels.ToArray ();
|
||||
|
||||
Assert.Equal ("w4", tops [0].Id);
|
||||
Assert.Equal ("w3", tops [1].Id);
|
||||
@@ -148,16 +150,16 @@ public class StackExtensionsTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Stack_Toplevels_Swap ()
|
||||
public void Stack_topLevels_Swap ()
|
||||
{
|
||||
Stack<Toplevel> Toplevels = CreateToplevels ();
|
||||
Stack<Toplevel> topLevels = CreatetopLevels ();
|
||||
|
||||
var valueToSwapFrom = new Window { Id = "w3" };
|
||||
var valueToSwapTo = new Window { Id = "w1" };
|
||||
var comparer = new ToplevelEqualityComparer ();
|
||||
Toplevels.Swap (valueToSwapFrom, valueToSwapTo, comparer);
|
||||
topLevels.Swap (valueToSwapFrom, valueToSwapTo, comparer);
|
||||
|
||||
Toplevel [] tops = Toplevels.ToArray ();
|
||||
Toplevel [] tops = topLevels.ToArray ();
|
||||
|
||||
Assert.Equal ("w4", tops [0].Id);
|
||||
Assert.Equal ("w1", tops [1].Id);
|
||||
@@ -169,27 +171,27 @@ public class StackExtensionsTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
[Fact]
|
||||
public void ToplevelEqualityComparer_GetHashCode ()
|
||||
{
|
||||
Stack<Toplevel> Toplevels = CreateToplevels ();
|
||||
Stack<Toplevel> topLevels = CreatetopLevels ();
|
||||
|
||||
// Only allows unique keys
|
||||
HashSet<int> hCodes = new ();
|
||||
|
||||
foreach (Toplevel top in Toplevels)
|
||||
foreach (Toplevel top in topLevels)
|
||||
{
|
||||
Assert.True (hCodes.Add (top.GetHashCode ()));
|
||||
}
|
||||
}
|
||||
|
||||
private Stack<Toplevel> CreateToplevels ()
|
||||
private Stack<Toplevel> CreatetopLevels ()
|
||||
{
|
||||
Stack<Toplevel> Toplevels = new ();
|
||||
Stack<Toplevel> topLevels = new ();
|
||||
|
||||
Toplevels.Push (new Toplevel { Id = "Top" });
|
||||
Toplevels.Push (new Window { Id = "w1" });
|
||||
Toplevels.Push (new Window { Id = "w2" });
|
||||
Toplevels.Push (new Window { Id = "w3" });
|
||||
Toplevels.Push (new Window { Id = "w4" });
|
||||
topLevels.Push (new Toplevel { Id = "Top" });
|
||||
topLevels.Push (new Window { Id = "w1" });
|
||||
topLevels.Push (new Window { Id = "w2" });
|
||||
topLevels.Push (new Window { Id = "w3" });
|
||||
topLevels.Push (new Window { Id = "w4" });
|
||||
|
||||
return Toplevels;
|
||||
return topLevels;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +226,9 @@ public class ScopeTests
|
||||
}));
|
||||
}
|
||||
|
||||
#pragma warning disable xUnit1031
|
||||
Task.WaitAll (tasks.ToArray ());
|
||||
#pragma warning restore xUnit1031
|
||||
|
||||
// Assert
|
||||
Assert.Equal (threadCount * itemsPerThread, scope.Count);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// Alias Console to MockConsole so we don't accidentally use Console
|
||||
|
||||
using UnitTests;
|
||||
|
||||
namespace UnitTests_Parallelizable.DrawingTests;
|
||||
|
||||
public class AttributeTests
|
||||
public class AttributeTests : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_ParsesNamedColorsAndStyle ()
|
||||
@@ -36,7 +38,7 @@ public class AttributeTests
|
||||
public void Constructor_DefaultsToNoneStyle_WhenStyleIsNullOrEmpty ()
|
||||
{
|
||||
var attr1 = new Attribute ("White", "Black");
|
||||
var attr2 = new Attribute ("White", "Black", null);
|
||||
var attr2 = new Attribute ("White", "Black");
|
||||
var attr3 = new Attribute ("White", "Black", "");
|
||||
Assert.Equal (TextStyle.None, attr1.Style);
|
||||
Assert.Equal (TextStyle.None, attr2.Style);
|
||||
@@ -103,8 +105,7 @@ public class AttributeTests
|
||||
[Fact]
|
||||
public void Constructors_Construct ()
|
||||
{
|
||||
var driver = new FakeDriver ();
|
||||
driver.Init ();
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
// Test parameterless constructor
|
||||
var attr = new Attribute ();
|
||||
@@ -258,8 +259,7 @@ public class AttributeTests
|
||||
[Fact]
|
||||
public void Make_Creates ()
|
||||
{
|
||||
var driver = new FakeDriver ();
|
||||
driver.Init ();
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
var fg = new Color ();
|
||||
fg = new (Color.Red);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Text;
|
||||
using UnitTests;
|
||||
using UnitTests.Parallelizable;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace UnitTests_Parallelizable.DrawingTests;
|
||||
@@ -11,7 +10,7 @@ namespace UnitTests_Parallelizable.DrawingTests;
|
||||
/// Note: Tests that verify rendered output (ToString()) cannot be parallelized because LineCanvas
|
||||
/// depends on Application.Driver for glyph resolution and configuration. Those tests remain in UnitTests.
|
||||
/// </summary>
|
||||
public class LineCanvasTests (ITestOutputHelper output) : ParallelizableBase
|
||||
public class LineCanvasTests (ITestOutputHelper output) : FakeDriverBase
|
||||
{
|
||||
#region Basic API Tests
|
||||
|
||||
@@ -548,7 +547,7 @@ public class LineCanvasTests (ITestOutputHelper output) : ParallelizableBase
|
||||
string expected
|
||||
)
|
||||
{
|
||||
IConsoleDriver driver = CreateFakeDriver ();
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
View v = GetCanvas (driver, out LineCanvas lc);
|
||||
v.Width = 10;
|
||||
v.Height = 10;
|
||||
@@ -1483,7 +1482,7 @@ public class LineCanvasTests (ITestOutputHelper output) : ParallelizableBase
|
||||
/// <param name="offsetX">How far to offset drawing in X</param>
|
||||
/// <param name="offsetY">How far to offset drawing in Y</param>
|
||||
/// <returns></returns>
|
||||
private View GetCanvas (IConsoleDriver driver, out LineCanvas canvas, int offsetX = 0, int offsetY = 0)
|
||||
private View GetCanvas (IDriver driver, out LineCanvas canvas, int offsetX = 0, int offsetY = 0)
|
||||
{
|
||||
var v = new View { Width = 10, Height = 5, Viewport = new (0, 0, 10, 5) };
|
||||
v.Driver = driver;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Microsoft.VisualStudio.TestPlatform.Utilities;
|
||||
using UnitTests;
|
||||
using UnitTests.Parallelizable;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace UnitTests_Parallelizable.DrawingTests;
|
||||
@@ -11,7 +10,7 @@ namespace UnitTests_Parallelizable.DrawingTests;
|
||||
///
|
||||
/// Note: Tests that verify rendered output (Draw methods) require Application.Driver and remain in UnitTests as integration tests.
|
||||
/// </summary>
|
||||
public class RulerTests (ITestOutputHelper output): ParallelizableBase
|
||||
public class RulerTests (ITestOutputHelper output) : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_Defaults ()
|
||||
@@ -52,7 +51,7 @@ public class RulerTests (ITestOutputHelper output): ParallelizableBase
|
||||
[Fact]
|
||||
public void Draw_Default ()
|
||||
{
|
||||
IConsoleDriver driver = CreateFakeDriver ();
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
var r = new Ruler ();
|
||||
r.Draw (Point.Empty, driver: driver);
|
||||
@@ -62,7 +61,7 @@ public class RulerTests (ITestOutputHelper output): ParallelizableBase
|
||||
[Fact]
|
||||
public void Draw_Horizontal ()
|
||||
{
|
||||
IConsoleDriver driver = CreateFakeDriver ();
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
var len = 15;
|
||||
|
||||
@@ -108,7 +107,7 @@ public class RulerTests (ITestOutputHelper output): ParallelizableBase
|
||||
[Fact]
|
||||
public void Draw_Vertical ()
|
||||
{
|
||||
IConsoleDriver driver = CreateFakeDriver ();
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
var len = 15;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Xunit.Abstractions;
|
||||
using UnitTests;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace UnitTests.DrawingTests;
|
||||
namespace UnitTests_Parallelizable.DrawingTests;
|
||||
|
||||
public class StraightLineExtensionsTests (ITestOutputHelper output)
|
||||
{
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using System.Text;
|
||||
using UnitTests;
|
||||
using UnitTests.Parallelizable;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace UnitTests_Parallelizable.DrawingTests;
|
||||
|
||||
public class ThicknessTests (ITestOutputHelper output) : ParallelizableBase
|
||||
public class ThicknessTests (ITestOutputHelper output) : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_Defaults ()
|
||||
@@ -625,7 +624,7 @@ public class ThicknessTests (ITestOutputHelper output) : ParallelizableBase
|
||||
[Fact]
|
||||
public void DrawTests ()
|
||||
{
|
||||
IConsoleDriver driver = new FakeDriver ();
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.SetScreenSize (60, 40);
|
||||
|
||||
var t = new Thickness (0, 0, 0, 0);
|
||||
@@ -738,7 +737,7 @@ public class ThicknessTests (ITestOutputHelper output) : ParallelizableBase
|
||||
[Fact]
|
||||
public void DrawTests_Ruler ()
|
||||
{
|
||||
IConsoleDriver driver = new FakeDriver ();
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
// Add a frame so we can see the ruler
|
||||
var f = new FrameView { X = 0, Y = 0, Width = Dim.Fill (), Height = Dim.Fill (), BorderStyle = LineStyle.Single };
|
||||
|
||||
180
Tests/UnitTestsParallelizable/Drivers/AddRuneTests.cs
Normal file
180
Tests/UnitTestsParallelizable/Drivers/AddRuneTests.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System.Buffers;
|
||||
using System.Text;
|
||||
using UnitTests;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
// Alias Console to MockConsole so we don't accidentally use Console
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
public class AddRuneTests (ITestOutputHelper output) : FakeDriverBase
|
||||
{
|
||||
private readonly ITestOutputHelper _output = output;
|
||||
|
||||
[Fact]
|
||||
public void AddRune ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
driver.Rows = 25;
|
||||
driver.Cols = 80;
|
||||
driver.AddRune (new Rune ('a'));
|
||||
Assert.Equal ((Rune)'a', driver.Contents [0, 0].Rune);
|
||||
|
||||
driver.End ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRune_Accented_Letter_With_Three_Combining_Unicode_Chars ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
var expected = new Rune ('ắ');
|
||||
|
||||
var text = "\u1eaf";
|
||||
driver.AddStr (text);
|
||||
Assert.Equal (expected, driver.Contents [0, 0].Rune);
|
||||
Assert.Equal ((Rune)' ', driver.Contents [0, 1].Rune);
|
||||
|
||||
driver.ClearContents ();
|
||||
driver.Move (0, 0);
|
||||
|
||||
text = "\u0103\u0301";
|
||||
driver.AddStr (text);
|
||||
Assert.Equal (expected, driver.Contents [0, 0].Rune);
|
||||
Assert.Equal ((Rune)' ', driver.Contents [0, 1].Rune);
|
||||
|
||||
driver.ClearContents ();
|
||||
driver.Move (0, 0);
|
||||
|
||||
text = "\u0061\u0306\u0301";
|
||||
driver.AddStr (text);
|
||||
Assert.Equal (expected, driver.Contents [0, 0].Rune);
|
||||
Assert.Equal ((Rune)' ', driver.Contents [0, 1].Rune);
|
||||
|
||||
// var s = "a\u0301\u0300\u0306";
|
||||
|
||||
// DriverAsserts.AssertDriverContentsWithFrameAre (@"
|
||||
//ắ", output);
|
||||
|
||||
// tf.Text = "\u1eaf";
|
||||
// Application.Refresh ();
|
||||
// DriverAsserts.AssertDriverContentsWithFrameAre (@"
|
||||
//ắ", output);
|
||||
|
||||
// tf.Text = "\u0103\u0301";
|
||||
// Application.Refresh ();
|
||||
// DriverAsserts.AssertDriverContentsWithFrameAre (@"
|
||||
//ắ", output);
|
||||
|
||||
// tf.Text = "\u0061\u0306\u0301";
|
||||
// Application.Refresh ();
|
||||
// DriverAsserts.AssertDriverContentsWithFrameAre (@"
|
||||
//ắ", output);
|
||||
driver.End ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRune_InvalidLocation_DoesNothing ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
driver.Move (driver.Cols, driver.Rows);
|
||||
driver.AddRune ('a');
|
||||
|
||||
for (var col = 0; col < driver.Cols; col++)
|
||||
{
|
||||
for (var row = 0; row < driver.Rows; row++)
|
||||
{
|
||||
Assert.Equal ((Rune)' ', driver.Contents [row, col].Rune);
|
||||
}
|
||||
}
|
||||
|
||||
driver.End ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRune_MovesToNextColumn ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
driver.AddRune ('a');
|
||||
Assert.Equal ((Rune)'a', driver.Contents [0, 0].Rune);
|
||||
Assert.Equal (0, driver.Row);
|
||||
Assert.Equal (1, driver.Col);
|
||||
|
||||
driver.AddRune ('b');
|
||||
Assert.Equal ((Rune)'b', driver.Contents [0, 1].Rune);
|
||||
Assert.Equal (0, driver.Row);
|
||||
Assert.Equal (2, driver.Col);
|
||||
|
||||
// Move to the last column of the first row
|
||||
int lastCol = driver.Cols - 1;
|
||||
driver.Move (lastCol, 0);
|
||||
Assert.Equal (0, driver.Row);
|
||||
Assert.Equal (lastCol, driver.Col);
|
||||
|
||||
// Add a rune to the last column of the first row; should increment the row or col even though it's now invalid
|
||||
driver.AddRune ('c');
|
||||
Assert.Equal ((Rune)'c', driver.Contents [0, lastCol].Rune);
|
||||
Assert.Equal (lastCol + 1, driver.Col);
|
||||
|
||||
// Add a rune; should succeed but do nothing as it's outside of Contents
|
||||
driver.AddRune ('d');
|
||||
Assert.Equal (lastCol + 2, driver.Col);
|
||||
|
||||
for (var col = 0; col < driver.Cols; col++)
|
||||
{
|
||||
for (var row = 0; row < driver.Rows; row++)
|
||||
{
|
||||
Assert.NotEqual ((Rune)'d', driver.Contents [row, col].Rune);
|
||||
}
|
||||
}
|
||||
|
||||
driver.End ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRune_MovesToNextColumn_Wide ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
// 🍕 Slice of Pizza "\U0001F355"
|
||||
OperationStatus operationStatus = Rune.DecodeFromUtf16 ("\U0001F355", out Rune rune, out int charsConsumed);
|
||||
Assert.Equal (OperationStatus.Done, operationStatus);
|
||||
Assert.Equal (charsConsumed, rune.Utf16SequenceLength);
|
||||
Assert.Equal (2, rune.GetColumns ());
|
||||
|
||||
driver.AddRune (rune);
|
||||
Assert.Equal (rune, driver.Contents [0, 0].Rune);
|
||||
Assert.Equal (0, driver.Row);
|
||||
Assert.Equal (2, driver.Col);
|
||||
|
||||
//driver.AddRune ('b');
|
||||
//Assert.Equal ((Rune)'b', driver.Contents [0, 1].Rune);
|
||||
//Assert.Equal (0, driver.Row);
|
||||
//Assert.Equal (2, driver.Col);
|
||||
|
||||
//// Move to the last column of the first row
|
||||
//var lastCol = driver.Cols - 1;
|
||||
//driver.Move (lastCol, 0);
|
||||
//Assert.Equal (0, driver.Row);
|
||||
//Assert.Equal (lastCol, driver.Col);
|
||||
|
||||
//// Add a rune to the last column of the first row; should increment the row or col even though it's now invalid
|
||||
//driver.AddRune ('c');
|
||||
//Assert.Equal ((Rune)'c', driver.Contents [0, lastCol].Rune);
|
||||
//Assert.Equal (lastCol + 1, driver.Col);
|
||||
|
||||
//// Add a rune; should succeed but do nothing as it's outside of Contents
|
||||
//driver.AddRune ('d');
|
||||
//Assert.Equal (lastCol + 2, driver.Col);
|
||||
//for (var col = 0; col < driver.Cols; col++) {
|
||||
// for (var row = 0; row < driver.Rows; row++) {
|
||||
// Assert.NotEqual ((Rune)'d', driver.Contents [row, col].Rune);
|
||||
// }
|
||||
//}
|
||||
|
||||
driver.End ();
|
||||
}
|
||||
}
|
||||
130
Tests/UnitTestsParallelizable/Drivers/AnsiKeyboardParserTests.cs
Normal file
130
Tests/UnitTestsParallelizable/Drivers/AnsiKeyboardParserTests.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
#nullable enable
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
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 ["\u001b[A", Key.CursorUp];
|
||||
yield return ["\u001b[B", Key.CursorDown];
|
||||
yield return ["\u001b[C", Key.CursorRight];
|
||||
yield return ["\u001b[D", Key.CursorLeft];
|
||||
|
||||
// Valid inputs with modifiers
|
||||
yield return ["\u001b[1;2A", Key.CursorUp.WithShift];
|
||||
yield return ["\u001b[1;3A", Key.CursorUp.WithAlt];
|
||||
yield return ["\u001b[1;4A", Key.CursorUp.WithAlt.WithShift];
|
||||
yield return ["\u001b[1;5A", Key.CursorUp.WithCtrl];
|
||||
yield return ["\u001b[1;6A", Key.CursorUp.WithCtrl.WithShift];
|
||||
yield return ["\u001b[1;7A", Key.CursorUp.WithCtrl.WithAlt];
|
||||
yield return ["\u001b[1;8A", Key.CursorUp.WithCtrl.WithAlt.WithShift];
|
||||
|
||||
yield return ["\u001b[1;2B", Key.CursorDown.WithShift];
|
||||
yield return ["\u001b[1;3B", Key.CursorDown.WithAlt];
|
||||
yield return ["\u001b[1;4B", Key.CursorDown.WithAlt.WithShift];
|
||||
yield return ["\u001b[1;5B", Key.CursorDown.WithCtrl];
|
||||
yield return ["\u001b[1;6B", Key.CursorDown.WithCtrl.WithShift];
|
||||
yield return ["\u001b[1;7B", Key.CursorDown.WithCtrl.WithAlt];
|
||||
yield return ["\u001b[1;8B", Key.CursorDown.WithCtrl.WithAlt.WithShift];
|
||||
|
||||
yield return ["\u001b[1;2C", Key.CursorRight.WithShift];
|
||||
yield return ["\u001b[1;3C", Key.CursorRight.WithAlt];
|
||||
yield return ["\u001b[1;4C", Key.CursorRight.WithAlt.WithShift];
|
||||
yield return ["\u001b[1;5C", Key.CursorRight.WithCtrl];
|
||||
yield return ["\u001b[1;6C", Key.CursorRight.WithCtrl.WithShift];
|
||||
yield return ["\u001b[1;7C", Key.CursorRight.WithCtrl.WithAlt];
|
||||
yield return ["\u001b[1;8C", Key.CursorRight.WithCtrl.WithAlt.WithShift];
|
||||
|
||||
yield return ["\u001b[1;2D", Key.CursorLeft.WithShift];
|
||||
yield return ["\u001b[1;3D", Key.CursorLeft.WithAlt];
|
||||
yield return ["\u001b[1;4D", Key.CursorLeft.WithAlt.WithShift];
|
||||
yield return ["\u001b[1;5D", Key.CursorLeft.WithCtrl];
|
||||
yield return ["\u001b[1;6D", Key.CursorLeft.WithCtrl.WithShift];
|
||||
yield return ["\u001b[1;7D", Key.CursorLeft.WithCtrl.WithAlt];
|
||||
yield return ["\u001b[1;8D", Key.CursorLeft.WithCtrl.WithAlt.WithShift];
|
||||
|
||||
// Invalid inputs
|
||||
yield return ["\u001b[Z", null!];
|
||||
yield return ["\u001b[invalid", null!];
|
||||
yield return ["\u001b[1", null!];
|
||||
yield return ["\u001b[AB", null!];
|
||||
yield return ["\u001b[;A", null!];
|
||||
|
||||
// Test data for various ANSI escape sequences and their expected Key values
|
||||
yield return ["\u001b[3;5~", Key.Delete.WithCtrl];
|
||||
|
||||
// Additional special keys
|
||||
yield return ["\u001b[H", Key.Home];
|
||||
yield return ["\u001b[F", Key.End];
|
||||
yield return ["\u001b[2~", Key.InsertChar];
|
||||
yield return ["\u001b[5~", Key.PageUp];
|
||||
yield return ["\u001b[6~", Key.PageDown];
|
||||
|
||||
// Home, End with modifiers
|
||||
yield return ["\u001b[1;2H", Key.Home.WithShift];
|
||||
yield return ["\u001b[1;3H", Key.Home.WithAlt];
|
||||
yield return ["\u001b[1;5F", Key.End.WithCtrl];
|
||||
|
||||
// Insert with modifiers
|
||||
yield return ["\u001b[2;2~", Key.InsertChar.WithShift];
|
||||
yield return ["\u001b[2;3~", Key.InsertChar.WithAlt];
|
||||
yield return ["\u001b[2;5~", Key.InsertChar.WithCtrl];
|
||||
|
||||
// PageUp/PageDown with modifiers
|
||||
yield return ["\u001b[5;2~", Key.PageUp.WithShift];
|
||||
yield return ["\u001b[6;3~", Key.PageDown.WithAlt];
|
||||
yield return ["\u001b[6;5~", Key.PageDown.WithCtrl];
|
||||
|
||||
// Function keys F1-F4 (common ESC O sequences)
|
||||
yield return ["\u001bOP", Key.F1];
|
||||
yield return ["\u001bOQ", Key.F2];
|
||||
yield return ["\u001bOR", Key.F3];
|
||||
yield return ["\u001bOS", Key.F4];
|
||||
|
||||
// Extended function keys F1-F12 with CSI sequences
|
||||
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];
|
||||
|
||||
// Function keys with modifiers
|
||||
yield return ["\u001b[1;2P", Key.F1.WithShift];
|
||||
yield return ["\u001b[1;3Q", Key.F2.WithAlt];
|
||||
yield return ["\u001b[1;5R", Key.F3.WithCtrl];
|
||||
|
||||
// Keys with Alt modifiers
|
||||
yield return ["\u001ba", Key.A.WithAlt, true];
|
||||
yield return ["\u001bA", Key.A.WithShift.WithAlt, true];
|
||||
yield return ["\u001b1", Key.D1.WithAlt, true];
|
||||
|
||||
// Keys with Ctrl and Alt modifiers
|
||||
yield return ["\u001b\u0001", Key.A.WithCtrl.WithAlt, true];
|
||||
yield return ["\u001b\u001a", Key.Z.WithCtrl.WithAlt, true];
|
||||
|
||||
// Keys with Ctrl, Shift and Alt modifiers
|
||||
yield return ["\u001b\u001f", Key.D7.WithCtrl.WithShift.WithAlt, true];
|
||||
}
|
||||
|
||||
// Consolidated test for all keyboard events (e.g., arrow keys)
|
||||
[Theory]
|
||||
[MemberData (nameof (GetKeyboardTestData))]
|
||||
public void ProcessKeyboardInput_ReturnsCorrectKey (string? input, Key? expectedKey, bool isLastMinute = false)
|
||||
{
|
||||
// Act
|
||||
Key? result = _parser.IsKeyboard (input, isLastMinute)?.GetKey (input);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedKey, result); // Verify the returned key matches the expected one
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
public class AnsiMouseParserTests
|
||||
{
|
||||
private readonly AnsiMouseParser _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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
using Moq;
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
public class AnsiRequestSchedulerTests
|
||||
{
|
||||
private readonly Mock<IAnsiResponseParser> _parserMock;
|
||||
private readonly AnsiRequestScheduler _scheduler;
|
||||
|
||||
private static DateTime _staticNow; // Static value to hold the current time
|
||||
|
||||
public AnsiRequestSchedulerTests ()
|
||||
{
|
||||
_parserMock = new (MockBehavior.Strict);
|
||||
_staticNow = DateTime.UtcNow; // Initialize static time
|
||||
_scheduler = new (_parserMock.Object, () => _staticNow);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendOrSchedule_SendsDeviceAttributeRequest_WhenNoOutstandingRequests ()
|
||||
{
|
||||
// Arrange
|
||||
var request = new AnsiEscapeSequenceRequest
|
||||
{
|
||||
Request = "\u001b[0c", // ESC [ c
|
||||
Terminator = "c",
|
||||
ResponseReceived = r => { }
|
||||
};
|
||||
|
||||
// we have no outstanding for c already
|
||||
_parserMock.Setup (p => p.IsExpecting ("c")).Returns (false).Verifiable (Times.Once);
|
||||
|
||||
// then we should execute our request
|
||||
_parserMock.Setup (p => p.ExpectResponse ("c", It.IsAny<Action<string>> (), null, false)).Verifiable (Times.Once);
|
||||
|
||||
// Act
|
||||
bool result = _scheduler.SendOrSchedule (request);
|
||||
|
||||
// Assert
|
||||
Assert.Empty (_scheduler.QueuedRequests); // We sent it i.e. we did not queue it for later
|
||||
Assert.True (result); // Should send immediately
|
||||
_parserMock.Verify ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendOrSchedule_QueuesRequest_WhenOutstandingRequestExists ()
|
||||
{
|
||||
// Arrange
|
||||
var request1 = new AnsiEscapeSequenceRequest
|
||||
{
|
||||
Request = "\u001b[0c", // ESC [ 0 c
|
||||
Terminator = "c",
|
||||
ResponseReceived = r => { }
|
||||
};
|
||||
|
||||
// Parser already has an ongoing request for "c"
|
||||
_parserMock.Setup (p => p.IsExpecting ("c")).Returns (true).Verifiable (Times.Once);
|
||||
|
||||
// Act
|
||||
bool result = _scheduler.SendOrSchedule (request1);
|
||||
|
||||
// Assert
|
||||
Assert.Single (_scheduler.QueuedRequests); // Ensure only one request is in the queue
|
||||
Assert.False (result); // Should be queued
|
||||
_parserMock.Verify ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RunSchedule_ThrottleNotExceeded_AllowSend ()
|
||||
{
|
||||
// Arrange
|
||||
var request = new AnsiEscapeSequenceRequest
|
||||
{
|
||||
Request = "\u001b[0c", // ESC [ 0 c
|
||||
Terminator = "c",
|
||||
ResponseReceived = r => { }
|
||||
};
|
||||
|
||||
// Set up to expect no outstanding request for "c" i.e. parser instantly gets response and resolves it
|
||||
_parserMock.Setup (p => p.IsExpecting ("c")).Returns (false).Verifiable (Times.Exactly (2));
|
||||
_parserMock.Setup (p => p.ExpectResponse ("c", It.IsAny<Action<string>> (), null, false)).Verifiable (Times.Exactly (2));
|
||||
|
||||
_scheduler.SendOrSchedule (request);
|
||||
|
||||
// Simulate time passing beyond throttle
|
||||
SetTime (101); // Exceed throttle limit
|
||||
|
||||
// Act
|
||||
|
||||
// Send another request after the throttled time limit
|
||||
bool result = _scheduler.SendOrSchedule (request);
|
||||
|
||||
// Assert
|
||||
Assert.Empty (_scheduler.QueuedRequests); // Should send and clear the request
|
||||
Assert.True (result); // Should have found and sent the request
|
||||
_parserMock.Verify ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RunSchedule_ThrottleExceeded_QueueRequest ()
|
||||
{
|
||||
// Arrange
|
||||
var request = new AnsiEscapeSequenceRequest
|
||||
{
|
||||
Request = "\u001b[0c", // ESC [ 0 c
|
||||
Terminator = "c",
|
||||
ResponseReceived = r => { }
|
||||
};
|
||||
|
||||
// Set up to expect no outstanding request for "c" i.e. parser instantly gets response and resolves it
|
||||
_parserMock.Setup (p => p.IsExpecting ("c")).Returns (false).Verifiable (Times.Exactly (2));
|
||||
_parserMock.Setup (p => p.ExpectResponse ("c", It.IsAny<Action<string>> (), null, false)).Verifiable (Times.Exactly (2));
|
||||
|
||||
_scheduler.SendOrSchedule (request);
|
||||
|
||||
// Simulate time passing
|
||||
SetTime (55); // Does not exceed throttle limit
|
||||
|
||||
// Act
|
||||
|
||||
// Send another request after the throttled time limit
|
||||
bool result = _scheduler.SendOrSchedule (request);
|
||||
|
||||
// Assert
|
||||
Assert.Single (_scheduler.QueuedRequests); // Should have been queued
|
||||
Assert.False (result); // Should have been queued
|
||||
|
||||
// Throttle still not exceeded
|
||||
Assert.False (_scheduler.RunSchedule ());
|
||||
|
||||
SetTime (90);
|
||||
|
||||
// Throttle still not exceeded
|
||||
Assert.False (_scheduler.RunSchedule ());
|
||||
|
||||
SetTime (105);
|
||||
|
||||
// Throttle exceeded - so send the request
|
||||
Assert.True (_scheduler.RunSchedule ());
|
||||
|
||||
_parserMock.Verify ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EvictStaleRequests_RemovesStaleRequest_AfterTimeout ()
|
||||
{
|
||||
// Arrange
|
||||
var request1 = new AnsiEscapeSequenceRequest
|
||||
{
|
||||
Request = "\u001b[0c",
|
||||
Terminator = "c",
|
||||
ResponseReceived = r => { }
|
||||
};
|
||||
|
||||
// Send
|
||||
_parserMock.Setup (p => p.IsExpecting ("c")).Returns (false).Verifiable (Times.Once);
|
||||
_parserMock.Setup (p => p.ExpectResponse ("c", It.IsAny<Action<string>> (), null, false)).Verifiable (Times.Exactly (2));
|
||||
|
||||
Assert.True (_scheduler.SendOrSchedule (request1));
|
||||
|
||||
// Parser already has an ongoing request for "c"
|
||||
_parserMock.Setup (p => p.IsExpecting ("c")).Returns (true).Verifiable (Times.Exactly (2));
|
||||
|
||||
// Cannot send because there is already outstanding request
|
||||
Assert.False (_scheduler.SendOrSchedule (request1));
|
||||
Assert.Single (_scheduler.QueuedRequests);
|
||||
|
||||
// Simulate request going stale
|
||||
SetTime (5001); // Exceeds stale timeout
|
||||
|
||||
// Parser should be told to give up on this one (evicted)
|
||||
_parserMock.Setup (p => p.StopExpecting ("c", false))
|
||||
.Callback (() =>
|
||||
{
|
||||
// When we tell parser to evict - it should now tell us it is no longer expecting
|
||||
_parserMock.Setup (p => p.IsExpecting ("c")).Returns (false).Verifiable (Times.Once);
|
||||
})
|
||||
.Verifiable ();
|
||||
|
||||
// When we send again the evicted one should be
|
||||
bool evicted = _scheduler.RunSchedule ();
|
||||
|
||||
Assert.True (evicted); // Stale request should be evicted
|
||||
Assert.Empty (_scheduler.QueuedRequests);
|
||||
|
||||
// Assert
|
||||
_parserMock.Verify ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RunSchedule_DoesNothing_WhenQueueIsEmpty ()
|
||||
{
|
||||
// Act
|
||||
bool result = _scheduler.RunSchedule ();
|
||||
|
||||
// Assert
|
||||
Assert.False (result); // No requests to process
|
||||
Assert.Empty (_scheduler.QueuedRequests);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SendOrSchedule_ManagesIndependentTerminatorsCorrectly ()
|
||||
{
|
||||
// Arrange
|
||||
var request1 = new AnsiEscapeSequenceRequest { Request = "\u001b[0c", Terminator = "c", ResponseReceived = r => { } };
|
||||
var request2 = new AnsiEscapeSequenceRequest { Request = "\u001b[0x", Terminator = "x", ResponseReceived = r => { } };
|
||||
|
||||
// Already have a 'c' ongoing
|
||||
_parserMock.Setup (p => p.IsExpecting ("c")).Returns (true).Verifiable (Times.Once);
|
||||
|
||||
// 'x' is free
|
||||
_parserMock.Setup (p => p.IsExpecting ("x")).Returns (false).Verifiable (Times.Once);
|
||||
_parserMock.Setup (p => p.ExpectResponse ("x", It.IsAny<Action<string>> (), null, false)).Verifiable (Times.Once);
|
||||
|
||||
// Act
|
||||
bool a = _scheduler.SendOrSchedule (request1);
|
||||
bool b = _scheduler.SendOrSchedule (request2);
|
||||
|
||||
// Assert
|
||||
Assert.False (a);
|
||||
Assert.True (b);
|
||||
Assert.Equal (request1, Assert.Single (_scheduler.QueuedRequests));
|
||||
_parserMock.Verify ();
|
||||
}
|
||||
|
||||
private void SetTime (int milliseconds)
|
||||
{
|
||||
// This simulates the passing of time by setting the Now function to return a specific time.
|
||||
DateTime newNow = _staticNow.AddMilliseconds (milliseconds);
|
||||
_scheduler.Now = () => newNow;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using Xunit.Abstractions;
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
// BUGBUG: These tests use TInputRecord of `int`, but that's not a realistic type for keyboard input.
|
||||
public class AnsiResponseParserTests (ITestOutputHelper output)
|
||||
{
|
||||
private readonly AnsiResponseParser<int> _parser1 = new ();
|
||||
422
Tests/UnitTestsParallelizable/Drivers/ConsoleKeyMappingTests.cs
Normal file
422
Tests/UnitTestsParallelizable/Drivers/ConsoleKeyMappingTests.cs
Normal file
@@ -0,0 +1,422 @@
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
public class ConsoleKeyMappingTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData ('a', ConsoleKey.A, false, false, false, (KeyCode)'a')]
|
||||
[InlineData ('A', ConsoleKey.A, true, false, false, KeyCode.A | KeyCode.ShiftMask)]
|
||||
[InlineData ('á', ConsoleKey.A, false, false, false, (KeyCode)'á')]
|
||||
[InlineData ('Á', ConsoleKey.A, true, false, false, (KeyCode)'Á' | KeyCode.ShiftMask)]
|
||||
[InlineData ('à', ConsoleKey.A, false, false, false, (KeyCode)'à')]
|
||||
[InlineData ('À', ConsoleKey.A, true, false, false, (KeyCode)'À' | KeyCode.ShiftMask)]
|
||||
[InlineData ('5', ConsoleKey.D5, false, false, false, KeyCode.D5)]
|
||||
[InlineData ('%', ConsoleKey.D5, true, false, false, (KeyCode)'%' | KeyCode.ShiftMask)]
|
||||
[InlineData ('€', ConsoleKey.D5, false, true, true, (KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask)]
|
||||
[InlineData ('?', ConsoleKey.Oem4, true, false, false, (KeyCode)'?' | KeyCode.ShiftMask)]
|
||||
[InlineData ('\'', ConsoleKey.Oem4, false, false, false, (KeyCode)'\'')]
|
||||
[InlineData ('q', ConsoleKey.Q, false, false, false, (KeyCode)'q')]
|
||||
[InlineData ('\0', ConsoleKey.F2, false, false, false, KeyCode.F2)]
|
||||
[InlineData ('英', ConsoleKey.None, false, false, false, (KeyCode)'英')]
|
||||
[InlineData ('\r', ConsoleKey.Enter, false, false, false, KeyCode.Enter)]
|
||||
public void MapConsoleKeyInfoToKeyCode_Also_Return_Modifiers (
|
||||
char keyChar,
|
||||
ConsoleKey consoleKey,
|
||||
bool shift,
|
||||
bool alt,
|
||||
bool control,
|
||||
KeyCode expectedKeyCode
|
||||
)
|
||||
{
|
||||
var consoleKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control);
|
||||
KeyCode keyCode = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (consoleKeyInfo);
|
||||
|
||||
Assert.Equal (keyCode, expectedKeyCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData ('a', false, false, false, (KeyCode)'a')]
|
||||
[InlineData ('A', true, false, false, KeyCode.A | KeyCode.ShiftMask)]
|
||||
[InlineData ('á', false, false, false, (KeyCode)'á')]
|
||||
[InlineData ('Á', true, false, false, (KeyCode)'Á' | KeyCode.ShiftMask)]
|
||||
[InlineData ('à', false, false, false, (KeyCode)'à')]
|
||||
[InlineData ('À', true, false, false, (KeyCode)'À' | KeyCode.ShiftMask)]
|
||||
[InlineData ('5', false, false, false, KeyCode.D5)]
|
||||
[InlineData ('%', true, false, false, (KeyCode)'%' | KeyCode.ShiftMask)]
|
||||
[InlineData ('€', false, true, true, (KeyCode)'€' | KeyCode.AltMask | KeyCode.CtrlMask)]
|
||||
[InlineData ('?', true, false, false, (KeyCode)'?' | KeyCode.ShiftMask)]
|
||||
[InlineData ('\'', false, false, false, (KeyCode)'\'')]
|
||||
[InlineData ('q', false, false, false, (KeyCode)'q')]
|
||||
[InlineData ((uint)KeyCode.F2, false, false, false, KeyCode.F2)]
|
||||
[InlineData ('英', false, false, false, (KeyCode)'英')]
|
||||
[InlineData ('\r', false, false, false, KeyCode.Enter)]
|
||||
[InlineData ('\n', false, false, false, (KeyCode)'\n')]
|
||||
public void MapToKeyCodeModifiers_Tests (
|
||||
uint keyChar,
|
||||
bool shift,
|
||||
bool alt,
|
||||
bool control,
|
||||
KeyCode expectedKeyCode
|
||||
)
|
||||
{
|
||||
ConsoleModifiers modifiers = ConsoleKeyMapping.GetModifiers (shift, alt, control);
|
||||
var keyCode = (KeyCode)keyChar;
|
||||
keyCode = ConsoleKeyMapping.MapToKeyCodeModifiers (modifiers, keyCode);
|
||||
|
||||
Assert.Equal (keyCode, expectedKeyCode);
|
||||
}
|
||||
|
||||
public static IEnumerable<object []> UnShiftedChars =>
|
||||
new List<object []>
|
||||
{
|
||||
new object [] { 'a', 'A', KeyCode.A | KeyCode.ShiftMask },
|
||||
new object [] { 'z', 'Z', KeyCode.Z | KeyCode.ShiftMask },
|
||||
new object [] { 'á', 'Á', (KeyCode)'Á' | KeyCode.ShiftMask },
|
||||
new object [] { 'à', 'À', (KeyCode)'À' | KeyCode.ShiftMask },
|
||||
new object [] { 'ý', 'Ý', (KeyCode)'Ý' | KeyCode.ShiftMask },
|
||||
new object [] { '1', '!', (KeyCode)'!' | KeyCode.ShiftMask },
|
||||
new object [] { '2', '"', (KeyCode)'"' | KeyCode.ShiftMask },
|
||||
new object [] { '3', '#', (KeyCode)'#' | KeyCode.ShiftMask },
|
||||
new object [] { '4', '$', (KeyCode)'$' | KeyCode.ShiftMask },
|
||||
new object [] { '5', '%', (KeyCode)'%' | KeyCode.ShiftMask },
|
||||
new object [] { '6', '&', (KeyCode)'&' | KeyCode.ShiftMask },
|
||||
new object [] { '7', '/', (KeyCode)'/' | KeyCode.ShiftMask },
|
||||
new object [] { '8', '(', (KeyCode)'(' | KeyCode.ShiftMask },
|
||||
new object [] { '9', ')', (KeyCode)')' | KeyCode.ShiftMask },
|
||||
new object [] { '0', '=', (KeyCode)'=' | KeyCode.ShiftMask },
|
||||
new object [] { '\\', '|', (KeyCode)'|' | KeyCode.ShiftMask },
|
||||
new object [] { '\'', '?', (KeyCode)'?' | KeyCode.ShiftMask },
|
||||
new object [] { '«', '»', (KeyCode)'»' | KeyCode.ShiftMask },
|
||||
new object [] { '+', '*', (KeyCode)'*' | KeyCode.ShiftMask },
|
||||
new object [] { '´', '`', (KeyCode)'`' | KeyCode.ShiftMask },
|
||||
new object [] { 'º', 'ª', (KeyCode)'ª' | KeyCode.ShiftMask },
|
||||
new object [] { '~', '^', (KeyCode)'^' | KeyCode.ShiftMask },
|
||||
new object [] { '<', '>', (KeyCode)'>' | KeyCode.ShiftMask },
|
||||
new object [] { ',', ';', (KeyCode)';' | KeyCode.ShiftMask },
|
||||
new object [] { '.', ':', (KeyCode)':' | KeyCode.ShiftMask },
|
||||
new object [] { '-', '_', (KeyCode)'_' | KeyCode.ShiftMask }
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[InlineData (KeyCode.A, false, false, false)] // Unshifted A (lowercase)
|
||||
[InlineData (KeyCode.B, false, false, false)]
|
||||
[InlineData (KeyCode.Z, false, false, false)]
|
||||
[InlineData (KeyCode.A | KeyCode.ShiftMask, true, false, false)] // Shifted A (uppercase)
|
||||
[InlineData (KeyCode.Z | KeyCode.ShiftMask, true, false, false)]
|
||||
[InlineData (KeyCode.A | KeyCode.CtrlMask, false, false, true)] // Ctrl+A
|
||||
[InlineData (KeyCode.A | KeyCode.AltMask, false, true, false)] // Alt+A
|
||||
[InlineData (KeyCode.A | KeyCode.ShiftMask | KeyCode.CtrlMask, true, false, true)] // Ctrl+Shift+A
|
||||
[InlineData (KeyCode.A | KeyCode.ShiftMask | KeyCode.AltMask, true, true, false)] // Alt+Shift+A
|
||||
[InlineData (KeyCode.A | KeyCode.CtrlMask | KeyCode.AltMask, false, true, true)] // Ctrl+Alt+A
|
||||
[InlineData (KeyCode.A | KeyCode.ShiftMask | KeyCode.CtrlMask | KeyCode.AltMask, true, true, true)] // All modifiers
|
||||
public void MapToConsoleModifiers_LetterKeys_ReturnsCorrectModifiers (
|
||||
KeyCode key,
|
||||
bool expectedShift,
|
||||
bool expectedAlt,
|
||||
bool expectedControl
|
||||
)
|
||||
{
|
||||
// Act
|
||||
ConsoleModifiers result = ConsoleKeyMapping.MapToConsoleModifiers (key);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedShift, result.HasFlag (ConsoleModifiers.Shift));
|
||||
Assert.Equal (expectedAlt, result.HasFlag (ConsoleModifiers.Alt));
|
||||
Assert.Equal (expectedControl, result.HasFlag (ConsoleModifiers.Control));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (KeyCode.A)] // 65 = 'A' in ASCII, but represents unshifted key
|
||||
[InlineData (KeyCode.B)]
|
||||
[InlineData (KeyCode.M)]
|
||||
[InlineData (KeyCode.Z)]
|
||||
public void MapToConsoleModifiers_UnshiftedLetterKeys_DoesNotSetShiftFlag (KeyCode key)
|
||||
{
|
||||
// This test verifies the BUGFIX: KeyCode.A-Z (65-90) represent UNSHIFTED keys,
|
||||
// even though their numeric values match uppercase ASCII characters.
|
||||
// The old code incorrectly checked char.IsUpper((char)key) which would fail this test.
|
||||
|
||||
// Act
|
||||
ConsoleModifiers result = ConsoleKeyMapping.MapToConsoleModifiers (key);
|
||||
|
||||
// Assert - Shift should NOT be set for unshifted letter keys
|
||||
Assert.False (result.HasFlag (ConsoleModifiers.Shift),
|
||||
$"Shift should not be set for unshifted {key}. The KeyCode value {(int)key} represents a lowercase, unshifted key.");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (KeyCode.D1, false)] // Unshifted number keys
|
||||
[InlineData (KeyCode.D5, false)]
|
||||
[InlineData (KeyCode.Space, false)]
|
||||
[InlineData (KeyCode.Enter, false)]
|
||||
[InlineData (KeyCode.Tab, false)]
|
||||
[InlineData (KeyCode.D1 | KeyCode.ShiftMask, true)] // Shifted number keys
|
||||
public void MapToConsoleModifiers_NonLetterKeys_ReturnsCorrectShiftState (KeyCode key, bool expectedShift)
|
||||
{
|
||||
// Act
|
||||
ConsoleModifiers result = ConsoleKeyMapping.MapToConsoleModifiers (key);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedShift, result.HasFlag (ConsoleModifiers.Shift));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (KeyCode.A, 'a', ConsoleKey.A, false)] // Unshifted A = lowercase 'a'
|
||||
[InlineData (KeyCode.B, 'b', ConsoleKey.B, false)]
|
||||
[InlineData (KeyCode.M, 'm', ConsoleKey.M, false)]
|
||||
[InlineData (KeyCode.Z, 'z', ConsoleKey.Z, false)]
|
||||
public void GetConsoleKeyInfoFromKeyCode_UnshiftedLetterKeys_ReturnsLowercaseChar (
|
||||
KeyCode key,
|
||||
char expectedChar,
|
||||
ConsoleKey expectedKey,
|
||||
bool expectedShift
|
||||
)
|
||||
{
|
||||
// This test verifies the BUGFIX: Key.A through Key.Z should produce lowercase characters
|
||||
// when no ShiftMask is set. The old code would incorrectly return uppercase 'A'.
|
||||
|
||||
// Act
|
||||
ConsoleKeyInfo result = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (key);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedChar, result.KeyChar);
|
||||
Assert.Equal (expectedKey, result.Key);
|
||||
Assert.Equal (expectedShift, (result.Modifiers & ConsoleModifiers.Shift) != 0);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (KeyCode.A | KeyCode.ShiftMask, 'A', ConsoleKey.A, true)] // Shifted A = uppercase 'A'
|
||||
[InlineData (KeyCode.B | KeyCode.ShiftMask, 'B', ConsoleKey.B, true)]
|
||||
[InlineData (KeyCode.M | KeyCode.ShiftMask, 'M', ConsoleKey.M, true)]
|
||||
[InlineData (KeyCode.Z | KeyCode.ShiftMask, 'Z', ConsoleKey.Z, true)]
|
||||
public void GetConsoleKeyInfoFromKeyCode_ShiftedLetterKeys_ReturnsUppercaseChar (
|
||||
KeyCode key,
|
||||
char expectedChar,
|
||||
ConsoleKey expectedKey,
|
||||
bool expectedShift
|
||||
)
|
||||
{
|
||||
// Act
|
||||
ConsoleKeyInfo result = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (key);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedChar, result.KeyChar);
|
||||
Assert.Equal (expectedKey, result.Key);
|
||||
Assert.Equal (expectedShift, (result.Modifiers & ConsoleModifiers.Shift) != 0);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (KeyCode.A | KeyCode.CtrlMask, ConsoleKey.A, false, false, true)]
|
||||
[InlineData (KeyCode.A | KeyCode.AltMask, ConsoleKey.A, false, true, false)]
|
||||
[InlineData (KeyCode.A | KeyCode.ShiftMask | KeyCode.CtrlMask, ConsoleKey.A, true, false, true)]
|
||||
[InlineData (KeyCode.Z | KeyCode.CtrlMask, ConsoleKey.Z, false, false, true)]
|
||||
public void GetConsoleKeyInfoFromKeyCode_LetterKeysWithModifiers_ReturnsCorrectModifiers (
|
||||
KeyCode key,
|
||||
ConsoleKey expectedConsoleKey,
|
||||
bool expectedShift,
|
||||
bool expectedAlt,
|
||||
bool expectedControl
|
||||
)
|
||||
{
|
||||
// Act
|
||||
ConsoleKeyInfo result = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (key);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedConsoleKey, result.Key);
|
||||
Assert.Equal (expectedShift, (result.Modifiers & ConsoleModifiers.Shift) != 0);
|
||||
Assert.Equal (expectedAlt, (result.Modifiers & ConsoleModifiers.Alt) != 0);
|
||||
Assert.Equal (expectedControl, (result.Modifiers & ConsoleModifiers.Control) != 0);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (KeyCode.Enter, '\r', ConsoleKey.Enter)]
|
||||
[InlineData (KeyCode.Tab, '\t', ConsoleKey.Tab)]
|
||||
[InlineData (KeyCode.Esc, '\u001B', ConsoleKey.Escape)]
|
||||
[InlineData (KeyCode.Backspace, '\b', ConsoleKey.Backspace)]
|
||||
[InlineData (KeyCode.Space, ' ', ConsoleKey.Spacebar)]
|
||||
public void GetConsoleKeyInfoFromKeyCode_SpecialKeys_ReturnsCorrectKeyChar (
|
||||
KeyCode key,
|
||||
char expectedChar,
|
||||
ConsoleKey expectedKey
|
||||
)
|
||||
{
|
||||
// Act
|
||||
ConsoleKeyInfo result = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (key);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedChar, result.KeyChar);
|
||||
Assert.Equal (expectedKey, result.Key);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (KeyCode.F1, ConsoleKey.F1)]
|
||||
[InlineData (KeyCode.F5, ConsoleKey.F5)]
|
||||
[InlineData (KeyCode.F12, ConsoleKey.F12)]
|
||||
[InlineData (KeyCode.CursorUp, ConsoleKey.UpArrow)]
|
||||
[InlineData (KeyCode.CursorDown, ConsoleKey.DownArrow)]
|
||||
[InlineData (KeyCode.CursorLeft, ConsoleKey.LeftArrow)]
|
||||
[InlineData (KeyCode.CursorRight, ConsoleKey.RightArrow)]
|
||||
[InlineData (KeyCode.Home, ConsoleKey.Home)]
|
||||
[InlineData (KeyCode.End, ConsoleKey.End)]
|
||||
[InlineData (KeyCode.PageUp, ConsoleKey.PageUp)]
|
||||
[InlineData (KeyCode.PageDown, ConsoleKey.PageDown)]
|
||||
[InlineData (KeyCode.Delete, ConsoleKey.Delete)]
|
||||
[InlineData (KeyCode.Insert, ConsoleKey.Insert)]
|
||||
public void GetConsoleKeyInfoFromKeyCode_NavigationAndFunctionKeys_ReturnsCorrectConsoleKey (
|
||||
KeyCode key,
|
||||
ConsoleKey expectedKey
|
||||
)
|
||||
{
|
||||
// Act
|
||||
ConsoleKeyInfo result = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (key);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedKey, result.Key);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (KeyCode.D0, '0', ConsoleKey.D0)]
|
||||
[InlineData (KeyCode.D1, '1', ConsoleKey.D1)]
|
||||
[InlineData (KeyCode.D5, '5', ConsoleKey.D5)]
|
||||
[InlineData (KeyCode.D9, '9', ConsoleKey.D9)]
|
||||
public void GetConsoleKeyInfoFromKeyCode_NumberKeys_ReturnsCorrectKeyChar (
|
||||
KeyCode key,
|
||||
char expectedChar,
|
||||
ConsoleKey expectedKey
|
||||
)
|
||||
{
|
||||
// Act
|
||||
ConsoleKeyInfo result = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (key);
|
||||
|
||||
// Assert
|
||||
Assert.Equal (expectedChar, result.KeyChar);
|
||||
Assert.Equal (expectedKey, result.Key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapToConsoleModifiers_AllLetterKeys_UnshiftedDoesNotSetShift ()
|
||||
{
|
||||
// This comprehensive test ensures ALL letter keys A-Z without ShiftMask
|
||||
// do not have Shift set in the returned modifiers.
|
||||
// This will fail if the old "char.IsUpper" check is present.
|
||||
|
||||
for (KeyCode key = KeyCode.A; key <= KeyCode.Z; key++)
|
||||
{
|
||||
// Act
|
||||
ConsoleModifiers result = ConsoleKeyMapping.MapToConsoleModifiers (key);
|
||||
|
||||
// Assert
|
||||
Assert.False (
|
||||
result.HasFlag (ConsoleModifiers.Shift),
|
||||
$"Shift should not be set for unshifted {key} (value {(int)key}). " +
|
||||
$"KeyCode.{key} represents a lowercase, unshifted key even though its numeric value is {(int)key}."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MapToConsoleModifiers_AllLetterKeysShifted_SetsShift ()
|
||||
{
|
||||
// Verify that WITH ShiftMask, all letter keys DO set Shift
|
||||
|
||||
for (KeyCode key = KeyCode.A; key <= KeyCode.Z; key++)
|
||||
{
|
||||
KeyCode shiftedKey = key | KeyCode.ShiftMask;
|
||||
|
||||
// Act
|
||||
ConsoleModifiers result = ConsoleKeyMapping.MapToConsoleModifiers (shiftedKey);
|
||||
|
||||
// Assert
|
||||
Assert.True (
|
||||
result.HasFlag (ConsoleModifiers.Shift),
|
||||
$"Shift should be set for {shiftedKey}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetConsoleKeyInfoFromKeyCode_AllUnshiftedLetterKeys_ReturnLowercaseChars ()
|
||||
{
|
||||
// This comprehensive test verifies ALL letter keys A-Z produce lowercase characters
|
||||
// when no ShiftMask is set. This is the KEY test that will fail with the old code.
|
||||
|
||||
for (KeyCode key = KeyCode.A; key <= KeyCode.Z; key++)
|
||||
{
|
||||
// Act
|
||||
ConsoleKeyInfo result = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (key);
|
||||
|
||||
// Calculate expected lowercase character
|
||||
char expectedChar = (char)('a' + (key - KeyCode.A));
|
||||
|
||||
// Assert
|
||||
Assert.True (
|
||||
char.IsLower (result.KeyChar),
|
||||
$"KeyChar for unshifted {key} should be lowercase, but got '{result.KeyChar}' (0x{(int)result.KeyChar:X2}). " +
|
||||
$"Expected lowercase '{expectedChar}' (0x{(int)expectedChar:X2})."
|
||||
);
|
||||
|
||||
Assert.Equal (
|
||||
expectedChar,
|
||||
result.KeyChar
|
||||
);
|
||||
|
||||
Assert.False (
|
||||
(result.Modifiers & ConsoleModifiers.Shift) != 0,
|
||||
$"Shift modifier should not be set for unshifted {key}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetConsoleKeyInfoFromKeyCode_AllShiftedLetterKeys_ReturnUppercaseChars ()
|
||||
{
|
||||
// Verify that WITH ShiftMask, all letter keys produce uppercase characters
|
||||
|
||||
for (KeyCode key = KeyCode.A; key <= KeyCode.Z; key++)
|
||||
{
|
||||
KeyCode shiftedKey = key | KeyCode.ShiftMask;
|
||||
|
||||
// Act
|
||||
ConsoleKeyInfo result = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (shiftedKey);
|
||||
|
||||
// Calculate expected uppercase character
|
||||
char expectedChar = (char)('A' + (key - KeyCode.A));
|
||||
|
||||
// Assert
|
||||
Assert.True (
|
||||
char.IsUpper (result.KeyChar),
|
||||
$"KeyChar for shifted {shiftedKey} should be uppercase, but got '{result.KeyChar}'"
|
||||
);
|
||||
|
||||
Assert.Equal (expectedChar, result.KeyChar);
|
||||
|
||||
Assert.True (
|
||||
(result.Modifiers & ConsoleModifiers.Shift) != 0,
|
||||
$"Shift modifier should be set for {shiftedKey}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (KeyCode.A, KeyCode.A | KeyCode.ShiftMask, 'a', 'A')] // Without vs With Shift
|
||||
[InlineData (KeyCode.M, KeyCode.M | KeyCode.ShiftMask, 'm', 'M')]
|
||||
[InlineData (KeyCode.Z, KeyCode.Z | KeyCode.ShiftMask, 'z', 'Z')]
|
||||
public void GetConsoleKeyInfoFromKeyCode_ShiftMaskChangesCase (
|
||||
KeyCode unshifted,
|
||||
KeyCode shifted,
|
||||
char expectedUnshiftedChar,
|
||||
char expectedShiftedChar
|
||||
)
|
||||
{
|
||||
// Act
|
||||
ConsoleKeyInfo unshiftedResult = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (unshifted);
|
||||
ConsoleKeyInfo shiftedResult = ConsoleKeyMapping.GetConsoleKeyInfoFromKeyCode (shifted);
|
||||
|
||||
// Assert - Unshifted should be lowercase
|
||||
Assert.Equal (expectedUnshiftedChar, unshiftedResult.KeyChar);
|
||||
Assert.False ((unshiftedResult.Modifiers & ConsoleModifiers.Shift) != 0);
|
||||
|
||||
// Assert - Shifted should be uppercase
|
||||
Assert.Equal (expectedShiftedChar, shiftedResult.KeyChar);
|
||||
Assert.True ((shiftedResult.Modifiers & ConsoleModifiers.Shift) != 0);
|
||||
}
|
||||
}
|
||||
137
Tests/UnitTestsParallelizable/Drivers/ContentsTests.cs
Normal file
137
Tests/UnitTestsParallelizable/Drivers/ContentsTests.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System.Text;
|
||||
using UnitTests;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
// Alias Console to MockConsole so we don't accidentally use Console
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
public class ContentsTests (ITestOutputHelper output) : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void AddStr_Combining_Character_1st_Column ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
var expected = "\u0301!";
|
||||
driver.AddStr ("\u0301!"); // acute accent + exclamation mark
|
||||
DriverAssert.AssertDriverContentsAre (expected, output, driver);
|
||||
|
||||
driver.End ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddStr_With_Combining_Characters ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
var acuteAccent = new Rune (0x0301); // Combining acute accent (é)
|
||||
string combined = "e" + acuteAccent;
|
||||
var expected = "é";
|
||||
|
||||
driver.AddStr (combined);
|
||||
DriverAssert.AssertDriverContentsAre (expected, output, driver);
|
||||
|
||||
// 3 char combine
|
||||
// a + ogonek + acute = <U+0061, U+0328, U+0301> ( ą́ )
|
||||
var oGonek = new Rune (0x0328); // Combining ogonek (a small hook or comma shape)
|
||||
combined = "a" + oGonek + acuteAccent;
|
||||
expected = ("a" + oGonek).Normalize (NormalizationForm.FormC); // See Issue #2616
|
||||
|
||||
driver.Move (0, 0);
|
||||
driver.AddStr (combined);
|
||||
DriverAssert.AssertDriverContentsAre (expected, output, driver);
|
||||
|
||||
// e + ogonek + acute = <U+0061, U+0328, U+0301> ( ę́́ )
|
||||
combined = "e" + oGonek + acuteAccent;
|
||||
expected = ("e" + oGonek).Normalize (NormalizationForm.FormC); // See Issue #2616
|
||||
|
||||
driver.Move (0, 0);
|
||||
driver.AddStr (combined);
|
||||
DriverAssert.AssertDriverContentsAre (expected, output, driver);
|
||||
|
||||
// i + ogonek + acute = <U+0061, U+0328, U+0301> ( į́́́ )
|
||||
combined = "i" + oGonek + acuteAccent;
|
||||
expected = ("i" + oGonek).Normalize (NormalizationForm.FormC); // See Issue #2616
|
||||
|
||||
driver.Move (0, 0);
|
||||
driver.AddStr (combined);
|
||||
DriverAssert.AssertDriverContentsAre (expected, output, driver);
|
||||
|
||||
// u + ogonek + acute = <U+0061, U+0328, U+0301> ( ų́́́́ )
|
||||
combined = "u" + oGonek + acuteAccent;
|
||||
expected = ("u" + oGonek).Normalize (NormalizationForm.FormC); // See Issue #2616
|
||||
|
||||
driver.Move (0, 0);
|
||||
driver.AddStr (combined);
|
||||
DriverAssert.AssertDriverContentsAre (expected, output, driver);
|
||||
|
||||
driver.End ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Move_Bad_Coordinates ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
Assert.Equal (0, driver.Col);
|
||||
Assert.Equal (0, driver.Row);
|
||||
|
||||
driver.Move (-1, 0);
|
||||
Assert.Equal (-1, driver.Col);
|
||||
Assert.Equal (0, driver.Row);
|
||||
|
||||
driver.Move (0, -1);
|
||||
Assert.Equal (0, driver.Col);
|
||||
Assert.Equal (-1, driver.Row);
|
||||
|
||||
driver.Move (driver.Cols, 0);
|
||||
Assert.Equal (driver.Cols, driver.Col);
|
||||
Assert.Equal (0, driver.Row);
|
||||
|
||||
driver.Move (0, driver.Rows);
|
||||
Assert.Equal (0, driver.Col);
|
||||
Assert.Equal (driver.Rows, driver.Row);
|
||||
|
||||
driver.Move (500, 500);
|
||||
Assert.Equal (500, driver.Col);
|
||||
Assert.Equal (500, driver.Row);
|
||||
driver.End ();
|
||||
}
|
||||
|
||||
// TODO: Add these unit tests
|
||||
|
||||
// AddRune moves correctly
|
||||
|
||||
// AddRune with wide characters are handled correctly
|
||||
|
||||
// AddRune with wide characters and Col < 0 are handled correctly
|
||||
|
||||
// AddRune with wide characters and Col == Cols - 1 are handled correctly
|
||||
|
||||
// AddRune with wide characters and Col == Cols are handled correctly
|
||||
|
||||
// AddStr moves correctly
|
||||
|
||||
// AddStr with wide characters moves correctly
|
||||
|
||||
// AddStr where Col is negative works
|
||||
|
||||
// AddStr where Col is negative and characters include wide / combining characters works
|
||||
|
||||
// AddStr where Col is near Cols and characters include wide / combining characters works
|
||||
|
||||
// Clipping works correctly
|
||||
|
||||
// Clipping works correctly with wide characters
|
||||
|
||||
// Clipping works correctly with combining characters
|
||||
|
||||
// Clipping works correctly with combining characters and wide characters
|
||||
|
||||
// ResizeScreen works correctly
|
||||
|
||||
// Refresh works correctly
|
||||
|
||||
// IsDirty tests
|
||||
}
|
||||
19
Tests/UnitTestsParallelizable/Drivers/DriverColorTests.cs
Normal file
19
Tests/UnitTestsParallelizable/Drivers/DriverColorTests.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
// Alias Console to MockConsole so we don't accidentally use Console
|
||||
|
||||
using UnitTests;
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
public class DriverColorTests : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void Force16Colors_Sets ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
driver.Force16Colors = true;
|
||||
Assert.True (driver.Force16Colors);
|
||||
|
||||
driver.End ();
|
||||
}
|
||||
}
|
||||
6
Tests/UnitTestsParallelizable/Drivers/DriverTests.cs
Normal file
6
Tests/UnitTestsParallelizable/Drivers/DriverTests.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
using UnitTests;
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
public class DriverTests : FakeDriverBase
|
||||
{ }
|
||||
@@ -1,8 +1,8 @@
|
||||
using UnitTests.Parallelizable;
|
||||
using UnitTests;
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
public class EscSeqRequestsTests : ParallelizableBase
|
||||
public class EscSeqRequestsTests : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void Add_Tests ()
|
||||
106
Tests/UnitTestsParallelizable/Drivers/EscSeqUtilsTests.cs
Normal file
106
Tests/UnitTestsParallelizable/Drivers/EscSeqUtilsTests.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System.Text;
|
||||
|
||||
// ReSharper disable HeuristicUnreachableCode
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
public class EscSeqUtilsTests
|
||||
{
|
||||
[Fact]
|
||||
public void Defaults_Values ()
|
||||
{
|
||||
Assert.Equal ('\x1b', EscSeqUtils.KeyEsc);
|
||||
Assert.Equal ("\x1b[", EscSeqUtils.CSI);
|
||||
Assert.Equal ("\x1b[?1003h", EscSeqUtils.CSI_EnableAnyEventMouse);
|
||||
Assert.Equal ("\x1b[?1006h", EscSeqUtils.CSI_EnableSgrExtModeMouse);
|
||||
Assert.Equal ("\x1b[?1015h", EscSeqUtils.CSI_EnableUrxvtExtModeMouse);
|
||||
Assert.Equal ("\x1b[?1003l", EscSeqUtils.CSI_DisableAnyEventMouse);
|
||||
Assert.Equal ("\x1b[?1006l", EscSeqUtils.CSI_DisableSgrExtModeMouse);
|
||||
Assert.Equal ("\x1b[?1015l", EscSeqUtils.CSI_DisableUrxvtExtModeMouse);
|
||||
Assert.Equal ("\x1b[?1003h\x1b[?1015h\u001b[?1006h", EscSeqUtils.CSI_EnableMouseEvents);
|
||||
Assert.Equal ("\x1b[?1003l\x1b[?1015l\u001b[?1006l", EscSeqUtils.CSI_DisableMouseEvents);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetConsoleInputKey_ConsoleKeyInfo ()
|
||||
{
|
||||
var cki = new ConsoleKeyInfo ('r', 0, false, false, false);
|
||||
var expectedCki = new ConsoleKeyInfo ('r', ConsoleKey.R, false, false, false);
|
||||
Assert.Equal (expectedCki.ToString(), EscSeqUtils.MapConsoleKeyInfo (cki).ToString ());
|
||||
|
||||
cki = new ('r', 0, true, false, false);
|
||||
expectedCki = new ('r', ConsoleKey.R, true, false, false);
|
||||
Assert.Equal (expectedCki.ToString(), EscSeqUtils.MapConsoleKeyInfo (cki).ToString ());
|
||||
|
||||
cki = new ('r', 0, false, true, false);
|
||||
expectedCki = new ('r', ConsoleKey.R, false, true, false);
|
||||
Assert.Equal (expectedCki.ToString(), EscSeqUtils.MapConsoleKeyInfo (cki).ToString ());
|
||||
|
||||
cki = new ('r', 0, false, false, true);
|
||||
expectedCki = new ('r', ConsoleKey.R, false, false, true);
|
||||
Assert.Equal (expectedCki.ToString(), EscSeqUtils.MapConsoleKeyInfo(cki).ToString());
|
||||
|
||||
cki = new ('r', 0, true, true, false);
|
||||
expectedCki = new ('r', ConsoleKey.R, true, true, false);
|
||||
Assert.Equal (expectedCki.ToString(), EscSeqUtils.MapConsoleKeyInfo (cki).ToString ());
|
||||
|
||||
cki = new ('r', 0, false, true, true);
|
||||
expectedCki = new ('r', ConsoleKey.R, false, true, true);
|
||||
Assert.Equal (expectedCki.ToString(), EscSeqUtils.MapConsoleKeyInfo(cki).ToString());
|
||||
|
||||
cki = new ('r', 0, true, true, true);
|
||||
expectedCki = new ('r', ConsoleKey.R, true, true, true);
|
||||
Assert.Equal (expectedCki.ToString(), EscSeqUtils.MapConsoleKeyInfo(cki).ToString());
|
||||
|
||||
cki = new ('\u0012', 0, false, false, false);
|
||||
expectedCki = new ('\u0012', ConsoleKey.R, false, false, true);
|
||||
Assert.Equal (expectedCki.ToString(), EscSeqUtils.MapConsoleKeyInfo(cki).ToString());
|
||||
|
||||
cki = new ('\0', (ConsoleKey)64, false, false, true);
|
||||
expectedCki = new ('\0', ConsoleKey.Spacebar, false, false, true);
|
||||
Assert.Equal (expectedCki.ToString(), EscSeqUtils.MapConsoleKeyInfo(cki).ToString());
|
||||
|
||||
cki = new ('\r', 0, false, false, false);
|
||||
expectedCki = new ('\r', ConsoleKey.Enter, false, false, false);
|
||||
Assert.Equal (expectedCki.ToString(), EscSeqUtils.MapConsoleKeyInfo(cki).ToString());
|
||||
|
||||
cki = new ('\u007f', 0, false, false, false);
|
||||
expectedCki = new ('\u007f', ConsoleKey.Backspace, false, false, false);
|
||||
Assert.Equal (expectedCki.ToString(), EscSeqUtils.MapConsoleKeyInfo(cki).ToString());
|
||||
|
||||
cki = new ('R', 0, false, false, false);
|
||||
expectedCki = new ('R', ConsoleKey.R, true, false, false);
|
||||
Assert.Equal (expectedCki.ToString(), EscSeqUtils.MapConsoleKeyInfo(cki).ToString());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (0, 0, $"{EscSeqUtils.CSI}0;0H")]
|
||||
[InlineData (int.MaxValue, int.MaxValue, $"{EscSeqUtils.CSI}2147483647;2147483647H")]
|
||||
[InlineData (int.MinValue, int.MinValue, $"{EscSeqUtils.CSI}-2147483648;-2147483648H")]
|
||||
public void CSI_WriteCursorPosition_ReturnsCorrectEscSeq (int row, int col, string expected)
|
||||
{
|
||||
StringBuilder builder = new();
|
||||
using StringWriter writer = new(builder);
|
||||
|
||||
EscSeqUtils.CSI_WriteCursorPosition (writer, row, col);
|
||||
|
||||
string actual = builder.ToString();
|
||||
Assert.Equal (expected, actual);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData ('\u001B', KeyCode.Esc)]
|
||||
[InlineData ('\r', KeyCode.Enter)]
|
||||
[InlineData ('1', KeyCode.D1)]
|
||||
[InlineData ('!', (KeyCode)'!')]
|
||||
[InlineData ('a', KeyCode.A)]
|
||||
[InlineData ('A', KeyCode.A | KeyCode.ShiftMask)]
|
||||
public void MapChar_Returns_Modifiers_If_Needed (char ch, KeyCode keyCode)
|
||||
{
|
||||
ConsoleKeyInfo cki = EscSeqUtils.MapChar (ch);
|
||||
Key key = EscSeqUtils.MapKey (cki);
|
||||
Key expectedKey = keyCode;
|
||||
|
||||
Assert.Equal (key, expectedKey);
|
||||
}
|
||||
}
|
||||
286
Tests/UnitTestsParallelizable/Drivers/FakeDriverTests.cs
Normal file
286
Tests/UnitTestsParallelizable/Drivers/FakeDriverTests.cs
Normal file
@@ -0,0 +1,286 @@
|
||||
using System.Text;
|
||||
using UnitTests;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the FakeDriver to ensure it works properly with the modern component factory architecture.
|
||||
/// </summary>
|
||||
public class FakeDriverTests (ITestOutputHelper output) : FakeDriverBase
|
||||
{
|
||||
private readonly ITestOutputHelper _output = output;
|
||||
|
||||
#region Basic FakeDriver Tests
|
||||
|
||||
[Fact]
|
||||
public void FakeDriver_Init_Works ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
Assert.IsAssignableFrom<IDriver> (driver);
|
||||
|
||||
_output.WriteLine ($"Driver type: {driver.GetType ().Name}");
|
||||
_output.WriteLine ($"Screen size: {driver.Screen}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
public void FakeDriver_Screen_Has_Default_Size ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
// Default size should be 80x25
|
||||
Assert.Equal (new (0, 0, 80, 25), driver.Screen);
|
||||
Assert.Equal (80, driver.Cols);
|
||||
Assert.Equal (25, driver.Rows);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
public void FakeDriver_Can_Resize ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
// Start with default size
|
||||
Assert.Equal (80, driver.Cols);
|
||||
Assert.Equal (25, driver.Rows);
|
||||
|
||||
// Resize to 100x30
|
||||
driver?.SetScreenSize (100, 30);
|
||||
|
||||
// Verify new size
|
||||
Assert.Equal (100, driver.Cols);
|
||||
Assert.Equal (30, driver.Rows);
|
||||
Assert.Equal (new (0, 0, 100, 30), driver.Screen);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region CreateFakeDriver Tests
|
||||
|
||||
[Fact]
|
||||
|
||||
public void SetupFakeDriver_Initializes_Driver_With_80x25 ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
Assert.NotNull (driver);
|
||||
Assert.Equal (new (0, 0, 80, 25), driver.Screen);
|
||||
Assert.Equal (80, driver.Cols);
|
||||
Assert.Equal (25, driver.Rows);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
public void SetupFakeDriver_Driver_Is_IDriver ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
Assert.NotNull (driver);
|
||||
|
||||
// Should be IDriver
|
||||
Assert.IsAssignableFrom<IDriver> (driver);
|
||||
|
||||
_output.WriteLine ($"Driver type: {driver.GetType ().Name}");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
public void SetupFakeDriver_Can_Set_Screen_Size ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
IDriver fakeDriver = driver;
|
||||
Assert.NotNull (fakeDriver);
|
||||
|
||||
fakeDriver!.SetScreenSize (100, 50);
|
||||
|
||||
Assert.Equal (100, driver.Cols);
|
||||
Assert.Equal (50, driver.Rows);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Clipboard Tests
|
||||
|
||||
[Fact]
|
||||
public void FakeDriver_Clipboard_Works_When_Enabled ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
Assert.NotNull (driver.Clipboard);
|
||||
Assert.True (driver.Clipboard.IsSupported);
|
||||
|
||||
// Set clipboard content
|
||||
driver.Clipboard.SetClipboardData ("Test content");
|
||||
|
||||
// Get clipboard content
|
||||
string content = driver.Clipboard.GetClipboardData ();
|
||||
Assert.Equal ("Test content", content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeDriver_Clipboard_GetClipboarData_Works ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
Assert.NotNull (driver.Clipboard);
|
||||
|
||||
driver.Clipboard.SetClipboardData ("test");
|
||||
Assert.Equal ("test", driver.Clipboard.GetClipboardData ());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Buffer and Fill Tests
|
||||
|
||||
[Fact]
|
||||
|
||||
public void FakeDriver_Can_Fill_Rectangle ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
// Verify driver is initialized with buffers
|
||||
Assert.NotNull (driver);
|
||||
Assert.NotNull (driver.Contents);
|
||||
|
||||
// Fill a rectangle
|
||||
var rect = new Rectangle (5, 5, 10, 5);
|
||||
driver.FillRect (rect, (Rune)'X');
|
||||
|
||||
// Verify the rectangle was filled
|
||||
for (int row = rect.Y; row < rect.Y + rect.Height; row++)
|
||||
{
|
||||
for (int col = rect.X; col < rect.X + rect.Width; col++)
|
||||
{
|
||||
Assert.Equal ((Rune)'X', driver.Contents [row, col].Rune);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
public void FakeDriver_Buffer_Integrity_After_Multiple_Resizes ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
// Start with default size
|
||||
Assert.Equal (80, driver.Cols);
|
||||
Assert.Equal (25, driver.Rows);
|
||||
|
||||
// Fill with a pattern
|
||||
driver.FillRect (new (0, 0, 10, 5), (Rune)'A');
|
||||
|
||||
// Resize
|
||||
driver?.SetScreenSize (100, 30);
|
||||
|
||||
// Verify new size
|
||||
Assert.Equal (100, driver.Cols);
|
||||
Assert.Equal (30, driver.Rows);
|
||||
|
||||
// Verify buffer is clean (no stale runes from previous size)
|
||||
Assert.NotNull (driver.Contents);
|
||||
Assert.Equal (30, driver.Contents!.GetLength (0));
|
||||
Assert.Equal (100, driver.Contents.GetLength (1));
|
||||
|
||||
// Fill with new pattern
|
||||
driver.FillRect (new (0, 0, 20, 10), (Rune)'B');
|
||||
|
||||
// Resize back
|
||||
driver?.SetScreenSize (80, 25);
|
||||
|
||||
// Verify size is back
|
||||
Assert.Equal (80, driver.Cols);
|
||||
Assert.Equal (25, driver.Rows);
|
||||
|
||||
// Verify buffer dimensions match
|
||||
Assert.Equal (25, driver.Contents.GetLength (0));
|
||||
Assert.Equal (80, driver.Contents.GetLength (1));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScreenChanged Event Tests
|
||||
|
||||
[Fact]
|
||||
|
||||
public void ScreenChanged_Event_Fires_On_SetScreenSize ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
var screenChangedFired = false;
|
||||
Size? newSize = null;
|
||||
|
||||
driver.SizeChanged += (sender, args) =>
|
||||
{
|
||||
screenChangedFired = true;
|
||||
newSize = args.Size;
|
||||
};
|
||||
|
||||
// Trigger resize using FakeResize which uses SetScreenSize internally
|
||||
driver?.SetScreenSize (100, 30);
|
||||
|
||||
// Verify event fired
|
||||
Assert.True (screenChangedFired);
|
||||
Assert.NotNull (newSize);
|
||||
Assert.Equal (100, newSize!.Value.Width);
|
||||
Assert.Equal (30, newSize.Value.Height);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
public void FakeResize_Triggers_ScreenChanged_And_Updates_Application_Screen ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
var screenChangedFired = false;
|
||||
Size? eventSize = null;
|
||||
|
||||
driver.SizeChanged += (sender, args) =>
|
||||
{
|
||||
screenChangedFired = true;
|
||||
eventSize = args.Size;
|
||||
};
|
||||
|
||||
// Use FakeResize helper
|
||||
driver?.SetScreenSize (120, 40);
|
||||
|
||||
// Verify event fired
|
||||
Assert.True (screenChangedFired);
|
||||
Assert.NotNull (eventSize);
|
||||
Assert.Equal (120, eventSize!.Value.Width);
|
||||
Assert.Equal (40, eventSize.Value.Height);
|
||||
|
||||
// Verify driver.Screen was updated
|
||||
Assert.Equal (new (0, 0, 120, 40), driver.Screen);
|
||||
Assert.Equal (120, driver.Cols);
|
||||
Assert.Equal (40, driver.Rows);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
public void SizeChanged_Event_Still_Fires_For_Compatibility ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
var sizeChangedFired = false;
|
||||
var screenChangedFired = false;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
driver.SizeChanged += (sender, args) => { sizeChangedFired = true; };
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
driver.SizeChanged += (sender, args) => { screenChangedFired = true; };
|
||||
|
||||
// Trigger resize using FakeResize
|
||||
driver?.SetScreenSize (90, 35);
|
||||
|
||||
// Both events should fire for compatibility
|
||||
Assert.True (sizeChangedFired);
|
||||
Assert.True (screenChangedFired);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
212
Tests/UnitTestsParallelizable/Drivers/KeyCodeTests.cs
Normal file
212
Tests/UnitTestsParallelizable/Drivers/KeyCodeTests.cs
Normal file
@@ -0,0 +1,212 @@
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
public class KeyCodeTests
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
public KeyCodeTests (ITestOutputHelper output) { _output = output; }
|
||||
|
||||
[Fact]
|
||||
public void Key_Enum_Ambiguity_Check ()
|
||||
{
|
||||
KeyCode key = KeyCode.Y | KeyCode.CtrlMask;
|
||||
|
||||
// This will not be well compared.
|
||||
Assert.True (key.HasFlag (KeyCode.Q | KeyCode.CtrlMask));
|
||||
Assert.True ((key & (KeyCode.Q | KeyCode.CtrlMask)) != 0);
|
||||
Assert.Equal (KeyCode.Y | KeyCode.CtrlMask, key);
|
||||
Assert.Equal ("Y, CtrlMask", key.ToString ());
|
||||
|
||||
// This will be well compared, because the Key.CtrlMask have a high value.
|
||||
Assert.False (key == Application.QuitKey);
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case KeyCode.Q | KeyCode.CtrlMask:
|
||||
// Never goes here.
|
||||
break;
|
||||
case KeyCode.Y | KeyCode.CtrlMask:
|
||||
Assert.True (key == (KeyCode.Y | KeyCode.CtrlMask));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Key_ToString ()
|
||||
{
|
||||
KeyCode k = KeyCode.Y | KeyCode.CtrlMask;
|
||||
Assert.Equal ("Y, CtrlMask", k.ToString ());
|
||||
|
||||
k = KeyCode.CtrlMask | KeyCode.Y;
|
||||
Assert.Equal ("Y, CtrlMask", k.ToString ());
|
||||
|
||||
k = KeyCode.Space;
|
||||
Assert.Equal ("Space", k.ToString ());
|
||||
|
||||
k = KeyCode.D;
|
||||
Assert.Equal ("D", k.ToString ());
|
||||
|
||||
k = (KeyCode)'d';
|
||||
Assert.Equal ("d", ((char)k).ToString ());
|
||||
|
||||
k = KeyCode.D;
|
||||
Assert.Equal ("D", k.ToString ());
|
||||
|
||||
// In a console this will always returns Key.D
|
||||
k = KeyCode.D | KeyCode.ShiftMask;
|
||||
Assert.Equal ("D, ShiftMask", k.ToString ());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KeyEnum_ShouldHaveCorrectValues ()
|
||||
{
|
||||
Assert.Equal (0, (int)KeyCode.Null);
|
||||
Assert.Equal (8, (int)KeyCode.Backspace);
|
||||
Assert.Equal (9, (int)KeyCode.Tab);
|
||||
|
||||
// Continue for other keys...
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimpleEnum_And_FlagedEnum ()
|
||||
{
|
||||
SimpleEnum simple = SimpleEnum.Three | SimpleEnum.Five;
|
||||
|
||||
// Nothing will not be well compared here.
|
||||
Assert.True (simple.HasFlag (SimpleEnum.Zero | SimpleEnum.Five));
|
||||
Assert.True (simple.HasFlag (SimpleEnum.One | SimpleEnum.Five));
|
||||
Assert.True (simple.HasFlag (SimpleEnum.Two | SimpleEnum.Five));
|
||||
Assert.True (simple.HasFlag (SimpleEnum.Three | SimpleEnum.Five));
|
||||
Assert.True (simple.HasFlag (SimpleEnum.Four | SimpleEnum.Five));
|
||||
Assert.True ((simple & (SimpleEnum.Zero | SimpleEnum.Five)) != 0);
|
||||
Assert.True ((simple & (SimpleEnum.One | SimpleEnum.Five)) != 0);
|
||||
Assert.True ((simple & (SimpleEnum.Two | SimpleEnum.Five)) != 0);
|
||||
Assert.True ((simple & (SimpleEnum.Three | SimpleEnum.Five)) != 0);
|
||||
Assert.True ((simple & (SimpleEnum.Four | SimpleEnum.Five)) != 0);
|
||||
Assert.Equal (7, (int)simple); // As it is not flagged only shows as number.
|
||||
Assert.Equal ("7", simple.ToString ());
|
||||
Assert.False (simple == (SimpleEnum.Zero | SimpleEnum.Five));
|
||||
Assert.False (simple == (SimpleEnum.One | SimpleEnum.Five));
|
||||
Assert.True (simple == (SimpleEnum.Two | SimpleEnum.Five));
|
||||
Assert.True (simple == (SimpleEnum.Three | SimpleEnum.Five));
|
||||
Assert.False (simple == (SimpleEnum.Four | SimpleEnum.Five));
|
||||
|
||||
FlaggedEnum flagged = FlaggedEnum.Three | FlaggedEnum.Five;
|
||||
|
||||
// Nothing will not be well compared here.
|
||||
Assert.True (flagged.HasFlag (FlaggedEnum.Zero | FlaggedEnum.Five));
|
||||
Assert.True (flagged.HasFlag (FlaggedEnum.One | FlaggedEnum.Five));
|
||||
Assert.True (flagged.HasFlag (FlaggedEnum.Two | FlaggedEnum.Five));
|
||||
Assert.True (flagged.HasFlag (FlaggedEnum.Three | FlaggedEnum.Five));
|
||||
Assert.True (flagged.HasFlag (FlaggedEnum.Four | FlaggedEnum.Five));
|
||||
Assert.True ((flagged & (FlaggedEnum.Zero | FlaggedEnum.Five)) != 0);
|
||||
Assert.True ((flagged & (FlaggedEnum.One | FlaggedEnum.Five)) != 0);
|
||||
Assert.True ((flagged & (FlaggedEnum.Two | FlaggedEnum.Five)) != 0);
|
||||
Assert.True ((flagged & (FlaggedEnum.Three | FlaggedEnum.Five)) != 0);
|
||||
Assert.True ((flagged & (FlaggedEnum.Four | FlaggedEnum.Five)) != 0);
|
||||
Assert.Equal (FlaggedEnum.Two | FlaggedEnum.Five, flagged); // As it is flagged shows as bitwise.
|
||||
Assert.Equal ("Two, Five", flagged.ToString ());
|
||||
Assert.False (flagged == (FlaggedEnum.Zero | FlaggedEnum.Five));
|
||||
Assert.False (flagged == (FlaggedEnum.One | FlaggedEnum.Five));
|
||||
Assert.True (flagged == (FlaggedEnum.Two | FlaggedEnum.Five));
|
||||
Assert.True (flagged == (FlaggedEnum.Three | FlaggedEnum.Five));
|
||||
Assert.False (flagged == (FlaggedEnum.Four | FlaggedEnum.Five));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimpleHighValueEnum_And_FlaggedHighValueEnum ()
|
||||
{
|
||||
SimpleHighValueEnum simple = SimpleHighValueEnum.Three | SimpleHighValueEnum.Last;
|
||||
|
||||
// This will not be well compared.
|
||||
Assert.True (simple.HasFlag (SimpleHighValueEnum.Zero | SimpleHighValueEnum.Last));
|
||||
Assert.True (simple.HasFlag (SimpleHighValueEnum.One | SimpleHighValueEnum.Last));
|
||||
Assert.True (simple.HasFlag (SimpleHighValueEnum.Two | SimpleHighValueEnum.Last));
|
||||
Assert.True (simple.HasFlag (SimpleHighValueEnum.Three | SimpleHighValueEnum.Last));
|
||||
Assert.False (simple.HasFlag (SimpleHighValueEnum.Four | SimpleHighValueEnum.Last));
|
||||
Assert.True ((simple & (SimpleHighValueEnum.Zero | SimpleHighValueEnum.Last)) != 0);
|
||||
Assert.True ((simple & (SimpleHighValueEnum.One | SimpleHighValueEnum.Last)) != 0);
|
||||
Assert.True ((simple & (SimpleHighValueEnum.Two | SimpleHighValueEnum.Last)) != 0);
|
||||
Assert.True ((simple & (SimpleHighValueEnum.Three | SimpleHighValueEnum.Last)) != 0);
|
||||
Assert.True ((simple & (SimpleHighValueEnum.Four | SimpleHighValueEnum.Last)) != 0);
|
||||
|
||||
// This will be well compared, because the SimpleHighValueEnum.Last have a high value.
|
||||
Assert.Equal (1073741827, (int)simple); // As it is not flagged only shows as number.
|
||||
Assert.Equal ("1073741827", simple.ToString ()); // As it is not flagged only shows as number.
|
||||
Assert.False (simple == (SimpleHighValueEnum.Zero | SimpleHighValueEnum.Last));
|
||||
Assert.False (simple == (SimpleHighValueEnum.One | SimpleHighValueEnum.Last));
|
||||
Assert.False (simple == (SimpleHighValueEnum.Two | SimpleHighValueEnum.Last));
|
||||
Assert.True (simple == (SimpleHighValueEnum.Three | SimpleHighValueEnum.Last));
|
||||
Assert.False (simple == (SimpleHighValueEnum.Four | SimpleHighValueEnum.Last));
|
||||
|
||||
FlaggedHighValueEnum flagged = FlaggedHighValueEnum.Three | FlaggedHighValueEnum.Last;
|
||||
|
||||
// This will not be well compared.
|
||||
Assert.True (flagged.HasFlag (FlaggedHighValueEnum.Zero | FlaggedHighValueEnum.Last));
|
||||
Assert.True (flagged.HasFlag (FlaggedHighValueEnum.One | FlaggedHighValueEnum.Last));
|
||||
Assert.True (flagged.HasFlag (FlaggedHighValueEnum.Two | FlaggedHighValueEnum.Last));
|
||||
Assert.True (flagged.HasFlag (FlaggedHighValueEnum.Three | FlaggedHighValueEnum.Last));
|
||||
Assert.False (flagged.HasFlag (FlaggedHighValueEnum.Four | FlaggedHighValueEnum.Last));
|
||||
Assert.True ((flagged & (FlaggedHighValueEnum.Zero | FlaggedHighValueEnum.Last)) != 0);
|
||||
Assert.True ((flagged & (FlaggedHighValueEnum.One | FlaggedHighValueEnum.Last)) != 0);
|
||||
Assert.True ((flagged & (FlaggedHighValueEnum.Two | FlaggedHighValueEnum.Last)) != 0);
|
||||
Assert.True ((flagged & (FlaggedHighValueEnum.Three | FlaggedHighValueEnum.Last)) != 0);
|
||||
Assert.True ((flagged & (FlaggedHighValueEnum.Four | FlaggedHighValueEnum.Last)) != 0);
|
||||
|
||||
// This will be well compared, because the SimpleHighValueEnum.Last have a high value.
|
||||
Assert.Equal (
|
||||
FlaggedHighValueEnum.Three | FlaggedHighValueEnum.Last,
|
||||
flagged
|
||||
); // As it is flagged shows as bitwise.
|
||||
Assert.Equal ("Three, Last", flagged.ToString ()); // As it is flagged shows as bitwise.
|
||||
Assert.False (flagged == (FlaggedHighValueEnum.Zero | FlaggedHighValueEnum.Last));
|
||||
Assert.False (flagged == (FlaggedHighValueEnum.One | FlaggedHighValueEnum.Last));
|
||||
Assert.False (flagged == (FlaggedHighValueEnum.Two | FlaggedHighValueEnum.Last));
|
||||
Assert.True (flagged == (FlaggedHighValueEnum.Three | FlaggedHighValueEnum.Last));
|
||||
Assert.False (flagged == (FlaggedHighValueEnum.Four | FlaggedHighValueEnum.Last));
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum FlaggedEnum
|
||||
{
|
||||
Zero,
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
Five
|
||||
}
|
||||
|
||||
[Flags]
|
||||
private enum FlaggedHighValueEnum
|
||||
{
|
||||
Zero,
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
Last = 0x40000000
|
||||
}
|
||||
|
||||
private enum SimpleEnum
|
||||
{
|
||||
Zero,
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
Five
|
||||
}
|
||||
|
||||
private enum SimpleHighValueEnum
|
||||
{
|
||||
Zero,
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
Last = 0x40000000
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,535 @@
|
||||
#nullable enable
|
||||
using System.Collections.Concurrent;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
/// <summary>
|
||||
/// Low-level tests for IInput and IOutput implementations across all drivers.
|
||||
/// These tests are designed to fail with good error messages when run in environments
|
||||
/// without a real terminal (like GitHub Actions).
|
||||
/// </summary>
|
||||
public class IInputOutputTests (ITestOutputHelper output)
|
||||
{
|
||||
private readonly ITestOutputHelper _output = output;
|
||||
|
||||
#region DotNet Driver Tests
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
public void NetInput_Constructor_DoesNotThrow_WhenNoTerminalAvailable ()
|
||||
{
|
||||
// Arrange & Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
using var input = new NetInput ();
|
||||
_output.WriteLine ("NetInput created successfully");
|
||||
});
|
||||
|
||||
// Assert
|
||||
if (exception != null)
|
||||
{
|
||||
_output.WriteLine ($"FAILED: NetInput constructor threw: {exception.GetType ().Name}: {exception.Message}");
|
||||
_output.WriteLine ($"Stack trace: {exception.StackTrace}");
|
||||
}
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
public void NetInput_Peek_DoesNotThrow_WhenNoTerminalAvailable ()
|
||||
{
|
||||
// Arrange
|
||||
using var input = new NetInput ();
|
||||
|
||||
// Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
bool hasInput = input.Peek ();
|
||||
_output.WriteLine ($"NetInput.Peek() returned: {hasInput}");
|
||||
});
|
||||
|
||||
// Assert
|
||||
if (exception != null)
|
||||
{
|
||||
_output.WriteLine ($"FAILED: NetInput.Peek() threw: {exception.GetType ().Name}: {exception.Message}");
|
||||
_output.WriteLine ($"Stack trace: {exception.StackTrace}");
|
||||
}
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
public void NetInput_Read_DoesNotThrow_WhenNoTerminalAvailable ()
|
||||
{
|
||||
// Arrange
|
||||
using var input = new NetInput ();
|
||||
|
||||
// Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
List<ConsoleKeyInfo> items = input.Read ().ToList ();
|
||||
_output.WriteLine ($"NetInput.Read() returned {items.Count} items");
|
||||
});
|
||||
|
||||
// Assert
|
||||
if (exception != null)
|
||||
{
|
||||
_output.WriteLine ($"FAILED: NetInput.Read() threw: {exception.GetType ().Name}: {exception.Message}");
|
||||
_output.WriteLine ($"Stack trace: {exception.StackTrace}");
|
||||
}
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
public void NetInput_Dispose_DoesNotThrow_WhenNoTerminalAvailable ()
|
||||
{
|
||||
// Arrange
|
||||
var input = new NetInput ();
|
||||
|
||||
// Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
input.Dispose ();
|
||||
_output.WriteLine ("NetInput disposed successfully");
|
||||
});
|
||||
|
||||
// Assert
|
||||
if (exception != null)
|
||||
{
|
||||
_output.WriteLine ($"FAILED: NetInput.Dispose() threw: {exception.GetType ().Name}: {exception.Message}");
|
||||
_output.WriteLine ($"Stack trace: {exception.StackTrace}");
|
||||
}
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
public void NetOutput_Constructor_DoesNotThrow_WhenNoTerminalAvailable ()
|
||||
{
|
||||
// Arrange & Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
using var output = new NetOutput ();
|
||||
_output.WriteLine ("NetOutput created successfully");
|
||||
});
|
||||
|
||||
// Assert
|
||||
if (exception != null)
|
||||
{
|
||||
_output.WriteLine ($"FAILED: NetOutput constructor threw: {exception.GetType ().Name}: {exception.Message}");
|
||||
_output.WriteLine ($"Stack trace: {exception.StackTrace}");
|
||||
}
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
public void NetOutput_GetSize_ReturnsDefaultSize_WhenNoTerminalAvailable ()
|
||||
{
|
||||
// Arrange
|
||||
using var output = new NetOutput ();
|
||||
|
||||
// Act
|
||||
Size size = default;
|
||||
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
size = output.GetSize ();
|
||||
_output.WriteLine ($"NetOutput.GetSize() returned: {size.Width}x{size.Height}");
|
||||
});
|
||||
|
||||
// Assert
|
||||
if (exception != null)
|
||||
{
|
||||
_output.WriteLine ($"FAILED: NetOutput.GetSize() threw: {exception.GetType ().Name}: {exception.Message}");
|
||||
_output.WriteLine ($"Stack trace: {exception.StackTrace}");
|
||||
}
|
||||
|
||||
Assert.Null (exception);
|
||||
Assert.True (size.Width > 0, "Width should be > 0");
|
||||
Assert.True (size.Height > 0, "Height should be > 0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
public void NetOutput_Write_DoesNotThrow_WhenNoTerminalAvailable ()
|
||||
{
|
||||
// Arrange
|
||||
using var output = new NetOutput ();
|
||||
|
||||
// Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
ReadOnlySpan<char> text = "Test".AsSpan ();
|
||||
output.Write (text);
|
||||
_output.WriteLine ("NetOutput.Write() succeeded");
|
||||
});
|
||||
|
||||
// Assert
|
||||
if (exception != null)
|
||||
{
|
||||
_output.WriteLine ($"FAILED: NetOutput.Write() threw: {exception.GetType ().Name}: {exception.Message}");
|
||||
_output.WriteLine ($"Stack trace: {exception.StackTrace}");
|
||||
}
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
public void NetOutput_SetCursorPosition_DoesNotThrow_WhenNoTerminalAvailable ()
|
||||
{
|
||||
// Arrange
|
||||
using var output = new NetOutput ();
|
||||
|
||||
// Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
output.SetCursorPosition (0, 0);
|
||||
_output.WriteLine ("NetOutput.SetCursorPosition() succeeded");
|
||||
});
|
||||
|
||||
// Assert
|
||||
if (exception != null)
|
||||
{
|
||||
_output.WriteLine ($"FAILED: NetOutput.SetCursorPosition() threw: {exception.GetType ().Name}: {exception.Message}");
|
||||
_output.WriteLine ($"Stack trace: {exception.StackTrace}");
|
||||
}
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
public void NetInputProcessor_Constructor_DoesNotThrow ()
|
||||
{
|
||||
// Arrange & Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
var processor = new NetInputProcessor (queue);
|
||||
_output.WriteLine ("NetInputProcessor created successfully");
|
||||
});
|
||||
|
||||
// Assert
|
||||
if (exception != null)
|
||||
{
|
||||
_output.WriteLine ($"FAILED: NetInputProcessor constructor threw: {exception.GetType ().Name}: {exception.Message}");
|
||||
_output.WriteLine ($"Stack trace: {exception.StackTrace}");
|
||||
}
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Unix Driver Tests
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
[Trait ("Platform", "Unix")]
|
||||
public void UnixInput_Constructor_DoesNotThrow_WhenNoTerminalAvailable ()
|
||||
{
|
||||
// Arrange & Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var input = new UnixInput ();
|
||||
_output.WriteLine ("UnixInput created successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_output.WriteLine ($"Expected failure on non-terminal: {ex.Message}");
|
||||
|
||||
throw new XunitException (
|
||||
$"UnixInput failed in non-terminal environment: {ex.Message}\nThis is expected in GitHub Actions. The driver should detect this and handle gracefully.");
|
||||
}
|
||||
});
|
||||
|
||||
// Assert
|
||||
if (exception != null && !(exception is XunitException))
|
||||
{
|
||||
_output.WriteLine ($"FAILED: UnixInput constructor threw: {exception.GetType ().Name}: {exception.Message}");
|
||||
_output.WriteLine ($"Stack trace: {exception.StackTrace}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
[Trait ("Platform", "Unix")]
|
||||
public void UnixOutput_Constructor_DoesNotThrow_WhenNoTerminalAvailable ()
|
||||
{
|
||||
if (OperatingSystem.IsWindows ())
|
||||
{
|
||||
_output.WriteLine ("Skipping Unix test on Windows");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Arrange & Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
using var output = new UnixOutput ();
|
||||
_output.WriteLine ("UnixOutput created successfully");
|
||||
});
|
||||
|
||||
// Assert
|
||||
if (exception != null)
|
||||
{
|
||||
_output.WriteLine ($"FAILED: UnixOutput constructor threw: {exception.GetType ().Name}: {exception.Message}");
|
||||
_output.WriteLine ($"Stack trace: {exception.StackTrace}");
|
||||
}
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
[Trait ("Platform", "Unix")]
|
||||
public void UnixOutput_GetSize_ReturnsDefaultSize_WhenNoTerminalAvailable ()
|
||||
{
|
||||
if (OperatingSystem.IsWindows ())
|
||||
{
|
||||
_output.WriteLine ("Skipping Unix test on Windows");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Arrange
|
||||
using var output = new UnixOutput ();
|
||||
|
||||
// Act
|
||||
Size size = default;
|
||||
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
size = output.GetSize ();
|
||||
_output.WriteLine ($"UnixOutput.GetSize() returned: {size.Width}x{size.Height}");
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Null (exception);
|
||||
Assert.Equal (80, size.Width);
|
||||
Assert.Equal (25, size.Height);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Windows Driver Tests
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
[Trait ("Platform", "Windows")]
|
||||
public void WindowsInput_Constructor_DoesNotThrow_WhenNoTerminalAvailable ()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows ())
|
||||
{
|
||||
_output.WriteLine ("Skipping Windows test on non-Windows");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Arrange & Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
using var input = new WindowsInput ();
|
||||
_output.WriteLine ("WindowsInput created successfully");
|
||||
});
|
||||
|
||||
// Assert
|
||||
if (exception != null)
|
||||
{
|
||||
_output.WriteLine ($"FAILED: WindowsInput constructor threw: {exception.GetType ().Name}: {exception.Message}");
|
||||
_output.WriteLine ($"Stack trace: {exception.StackTrace}");
|
||||
}
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
[Trait ("Platform", "Windows")]
|
||||
public void WindowsOutput_Constructor_DoesNotThrow_WhenNoTerminalAvailable ()
|
||||
{
|
||||
if (!OperatingSystem.IsWindows ())
|
||||
{
|
||||
_output.WriteLine ("Skipping Windows test on non-Windows");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Arrange & Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var output = new WindowsOutput ();
|
||||
_output.WriteLine ("WindowsOutput created successfully");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_output.WriteLine ($"WindowsOutput threw during construction: {ex.GetType ().Name}: {ex.Message}");
|
||||
|
||||
throw;
|
||||
}
|
||||
});
|
||||
|
||||
// Assert
|
||||
if (exception != null)
|
||||
{
|
||||
_output.WriteLine ($"FAILED: WindowsOutput constructor threw: {exception.GetType ().Name}: {exception.Message}");
|
||||
_output.WriteLine ($"Stack trace: {exception.StackTrace}");
|
||||
}
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fake Driver Tests
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
public void FakeInput_Constructor_DoesNotThrow ()
|
||||
{
|
||||
// Arrange & Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
using var input = new FakeInput ();
|
||||
_output.WriteLine ("FakeInput created successfully");
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
public void FakeOutput_Constructor_DoesNotThrow ()
|
||||
{
|
||||
// Arrange & Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
using var output = new FakeOutput ();
|
||||
_output.WriteLine ("FakeOutput created successfully");
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
public void FakeOutput_GetSize_ReturnsExpectedSize ()
|
||||
{
|
||||
// Arrange
|
||||
using var output = new FakeOutput ();
|
||||
|
||||
// Act
|
||||
Size size = output.GetSize ();
|
||||
_output.WriteLine ($"FakeOutput.GetSize() returned: {size.Width}x{size.Height}");
|
||||
|
||||
// Assert
|
||||
Assert.True (size.Width > 0);
|
||||
Assert.True (size.Height > 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Component Factory Tests
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
public void NetComponentFactory_CreateInput_DoesNotThrow ()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new NetComponentFactory ();
|
||||
|
||||
// Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
using IInput<ConsoleKeyInfo> input = factory.CreateInput ();
|
||||
_output.WriteLine ("NetComponentFactory.CreateInput() succeeded");
|
||||
});
|
||||
|
||||
// Assert
|
||||
if (exception != null)
|
||||
{
|
||||
_output.WriteLine ($"FAILED: NetComponentFactory.CreateInput() threw: {exception.GetType ().Name}: {exception.Message}");
|
||||
_output.WriteLine ($"Stack trace: {exception.StackTrace}");
|
||||
}
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
public void NetComponentFactory_CreateOutput_DoesNotThrow ()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new NetComponentFactory ();
|
||||
|
||||
// Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
using IOutput output = factory.CreateOutput ();
|
||||
_output.WriteLine ("NetComponentFactory.CreateOutput() succeeded");
|
||||
});
|
||||
|
||||
// Assert
|
||||
if (exception != null)
|
||||
{
|
||||
_output.WriteLine ($"FAILED: NetComponentFactory.CreateOutput() threw: {exception.GetType ().Name}: {exception.Message}");
|
||||
_output.WriteLine ($"Stack trace: {exception.StackTrace}");
|
||||
}
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
public void FakeComponentFactory_CreateInput_DoesNotThrow ()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new FakeComponentFactory ();
|
||||
|
||||
// Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
using IInput<ConsoleKeyInfo> input = factory.CreateInput ();
|
||||
_output.WriteLine ("FakeComponentFactory.CreateInput() succeeded");
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait ("Category", "LowLevelDriver")]
|
||||
public void FakeComponentFactory_CreateOutput_DoesNotThrow ()
|
||||
{
|
||||
// Arrange
|
||||
var factory = new FakeComponentFactory ();
|
||||
|
||||
// Act
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
using IOutput output = factory.CreateOutput ();
|
||||
_output.WriteLine ("FakeComponentFactory.CreateOutput() succeeded");
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
153
Tests/UnitTestsParallelizable/Drivers/MouseInterpreterTests.cs
Normal file
153
Tests/UnitTestsParallelizable/Drivers/MouseInterpreterTests.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
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 ();
|
||||
|
||||
// Act and Assert
|
||||
for (var i = 0; i < events.Count; i++)
|
||||
{
|
||||
MouseEventArgs [] 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 List<MouseEventArgs>
|
||||
{
|
||||
new () { Flags = MouseFlags.Button1Pressed },
|
||||
new ()
|
||||
},
|
||||
null,
|
||||
MouseFlags.Button1Clicked
|
||||
];
|
||||
|
||||
yield return
|
||||
[
|
||||
new List<MouseEventArgs>
|
||||
{
|
||||
new () { Flags = MouseFlags.Button1Pressed },
|
||||
new (),
|
||||
new () { Flags = MouseFlags.Button1Pressed },
|
||||
new ()
|
||||
},
|
||||
null,
|
||||
MouseFlags.Button1Clicked,
|
||||
null,
|
||||
MouseFlags.Button1DoubleClicked
|
||||
];
|
||||
|
||||
yield return
|
||||
[
|
||||
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 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 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 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 List<MouseEventArgs>
|
||||
{
|
||||
new () { Flags = MouseFlags.Button1Pressed, Position = new (10, 11) },
|
||||
new () { Position = new (10, 11) },
|
||||
|
||||
// Clicking the line below means no double click because it's a different location
|
||||
new () { Flags = MouseFlags.Button1Pressed, Position = new (10, 12) },
|
||||
new () { Position = new (10, 12) }
|
||||
},
|
||||
null,
|
||||
MouseFlags.Button1Clicked,
|
||||
null,
|
||||
MouseFlags.Button1Clicked //release is click because new position
|
||||
];
|
||||
}
|
||||
}
|
||||
119
Tests/UnitTestsParallelizable/Drivers/NetInputProcessorTests.cs
Normal file
119
Tests/UnitTestsParallelizable/Drivers/NetInputProcessorTests.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
public class NetInputProcessorTests
|
||||
{
|
||||
public static IEnumerable<object []> GetConsoleKeyInfoToKeyTestCases_Rune ()
|
||||
{
|
||||
yield return [new ConsoleKeyInfo ('C', ConsoleKey.None, false, false, false), new Rune ('C')];
|
||||
yield return [new ConsoleKeyInfo ('\\', ConsoleKey.Oem5, false, false, false), new Rune ('\\')];
|
||||
yield return [new ConsoleKeyInfo ('+', ConsoleKey.OemPlus, true, false, false), new Rune ('+')];
|
||||
yield return [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 ()
|
||||
{
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
|
||||
queue.Enqueue (new ('H', ConsoleKey.None, true, false, false));
|
||||
queue.Enqueue (new ('e', ConsoleKey.None, false, false, false));
|
||||
|
||||
var processor = new NetInputProcessor (queue);
|
||||
|
||||
List<Key> ups = new ();
|
||||
List<Key> downs = new ();
|
||||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Text;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace UnitTests_Parallelizable.ConoleDriverTests;
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
public class Osc8UrlLinkerTests (ITestOutputHelper output)
|
||||
{
|
||||
@@ -0,0 +1,75 @@
|
||||
using Moq;
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
public class WindowSizeMonitorTests
|
||||
{
|
||||
[Fact]
|
||||
public void TestWindowSizeMonitor_RaisesEventWhenChanges ()
|
||||
{
|
||||
Mock<IOutput> consoleOutput = new ();
|
||||
|
||||
Queue<Size> queue = new (
|
||||
[
|
||||
new (30, 20),
|
||||
new (20, 20)
|
||||
]);
|
||||
|
||||
consoleOutput.Setup (m => m.GetSize ())
|
||||
.Returns (queue.Dequeue);
|
||||
|
||||
var outputBuffer = Mock.Of<IOutputBuffer> ();
|
||||
|
||||
var monitor = new SizeMonitorImpl (consoleOutput.Object);
|
||||
|
||||
List<SizeChangedEventArgs> result = [];
|
||||
monitor.SizeChanged += (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 ()
|
||||
{
|
||||
Mock<IOutput> consoleOutput = new ();
|
||||
|
||||
Queue<Size> queue = new (
|
||||
[
|
||||
new (30, 20),
|
||||
new (30, 20)
|
||||
]);
|
||||
|
||||
consoleOutput.Setup (m => m.GetSize ())
|
||||
.Returns (queue.Dequeue);
|
||||
|
||||
var outputBuffer = Mock.Of<IOutputBuffer> ();
|
||||
|
||||
var monitor = new SizeMonitorImpl (consoleOutput.Object);
|
||||
|
||||
List<SizeChangedEventArgs> result = [];
|
||||
monitor.SizeChanged += (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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,492 @@
|
||||
using System.Collections.Concurrent;
|
||||
using InputRecord = Terminal.Gui.Drivers.WindowsConsole.InputRecord;
|
||||
using ButtonState = Terminal.Gui.Drivers.WindowsConsole.ButtonState;
|
||||
using EventFlags = Terminal.Gui.Drivers.WindowsConsole.EventFlags;
|
||||
using ControlKeyState = Terminal.Gui.Drivers.WindowsConsole.ControlKeyState;
|
||||
using MouseEventRecord = Terminal.Gui.Drivers.WindowsConsole.MouseEventRecord;
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
public class WindowsInputProcessorTests
|
||||
{
|
||||
[Fact]
|
||||
public void Test_ProcessQueue_CapitalHLowerE ()
|
||||
{
|
||||
ConcurrentQueue<InputRecord> queue = new ();
|
||||
|
||||
queue.Enqueue (
|
||||
new ()
|
||||
{
|
||||
EventType = WindowsConsole.EventType.Key,
|
||||
KeyEvent = new ()
|
||||
{
|
||||
bKeyDown = true,
|
||||
UnicodeChar = 'H',
|
||||
dwControlKeyState = ControlKeyState.CapslockOn,
|
||||
wVirtualKeyCode = (VK)72,
|
||||
wVirtualScanCode = 35
|
||||
}
|
||||
});
|
||||
|
||||
queue.Enqueue (
|
||||
new ()
|
||||
{
|
||||
EventType = WindowsConsole.EventType.Key,
|
||||
KeyEvent = new ()
|
||||
{
|
||||
bKeyDown = false,
|
||||
UnicodeChar = 'H',
|
||||
dwControlKeyState = ControlKeyState.CapslockOn,
|
||||
wVirtualKeyCode = (VK)72,
|
||||
wVirtualScanCode = 35
|
||||
}
|
||||
});
|
||||
|
||||
queue.Enqueue (
|
||||
new ()
|
||||
{
|
||||
EventType = WindowsConsole.EventType.Key,
|
||||
KeyEvent = new ()
|
||||
{
|
||||
bKeyDown = true,
|
||||
UnicodeChar = 'i',
|
||||
dwControlKeyState = ControlKeyState.NoControlKeyPressed,
|
||||
wVirtualKeyCode = (VK)73,
|
||||
wVirtualScanCode = 23
|
||||
}
|
||||
});
|
||||
|
||||
queue.Enqueue (
|
||||
new ()
|
||||
{
|
||||
EventType = WindowsConsole.EventType.Key,
|
||||
KeyEvent = new ()
|
||||
{
|
||||
bKeyDown = false,
|
||||
UnicodeChar = 'i',
|
||||
dwControlKeyState = ControlKeyState.NoControlKeyPressed,
|
||||
wVirtualKeyCode = (VK)73,
|
||||
wVirtualScanCode = 23
|
||||
}
|
||||
});
|
||||
|
||||
var processor = new WindowsInputProcessor (queue);
|
||||
|
||||
List<Key> ups = [];
|
||||
List<Key> downs = [];
|
||||
|
||||
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 ()
|
||||
{
|
||||
ConcurrentQueue<InputRecord> queue = new ();
|
||||
|
||||
queue.Enqueue (
|
||||
new ()
|
||||
{
|
||||
EventType = WindowsConsole.EventType.Mouse,
|
||||
MouseEvent = new ()
|
||||
{
|
||||
MousePosition = new (32, 31),
|
||||
ButtonState = ButtonState.NoButtonPressed,
|
||||
ControlKeyState = ControlKeyState.NoControlKeyPressed,
|
||||
EventFlags = EventFlags.MouseMoved
|
||||
}
|
||||
});
|
||||
|
||||
var processor = new WindowsInputProcessor (queue);
|
||||
|
||||
List<MouseEventArgs> mouseEvents = [];
|
||||
|
||||
processor.MouseEvent += (s, e) => { mouseEvents.Add (e); };
|
||||
|
||||
Assert.Empty (mouseEvents);
|
||||
|
||||
processor.ProcessQueue ();
|
||||
|
||||
MouseEventArgs s = Assert.Single (mouseEvents);
|
||||
Assert.Equal (MouseFlags.ReportMousePosition, s.Flags);
|
||||
Assert.Equal (s.ScreenPosition, new (32, 31));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (ButtonState.Button1Pressed, MouseFlags.Button1Pressed)]
|
||||
[InlineData (ButtonState.Button2Pressed, MouseFlags.Button2Pressed)]
|
||||
[InlineData (ButtonState.Button3Pressed, MouseFlags.Button3Pressed)]
|
||||
[InlineData (ButtonState.Button4Pressed, MouseFlags.Button4Pressed)]
|
||||
internal void Test_ProcessQueue_Mouse_Pressed (ButtonState state, MouseFlags expectedFlag)
|
||||
{
|
||||
ConcurrentQueue<InputRecord> queue = new ();
|
||||
|
||||
queue.Enqueue (
|
||||
new ()
|
||||
{
|
||||
EventType = WindowsConsole.EventType.Mouse,
|
||||
MouseEvent = new ()
|
||||
{
|
||||
MousePosition = new (32, 31),
|
||||
ButtonState = state,
|
||||
ControlKeyState = ControlKeyState.NoControlKeyPressed,
|
||||
EventFlags = EventFlags.MouseMoved
|
||||
}
|
||||
});
|
||||
|
||||
var processor = new WindowsInputProcessor (queue);
|
||||
|
||||
List<MouseEventArgs> mouseEvents = [];
|
||||
|
||||
processor.MouseEvent += (s, e) => { mouseEvents.Add (e); };
|
||||
|
||||
Assert.Empty (mouseEvents);
|
||||
|
||||
processor.ProcessQueue ();
|
||||
|
||||
MouseEventArgs s = Assert.Single (mouseEvents);
|
||||
Assert.Equal (s.Flags, MouseFlags.ReportMousePosition | expectedFlag);
|
||||
Assert.Equal (s.ScreenPosition, new (32, 31));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (100, MouseFlags.WheeledUp)]
|
||||
[InlineData (-100, MouseFlags.WheeledDown)]
|
||||
internal void Test_ProcessQueue_Mouse_Wheel (int wheelValue, MouseFlags expectedFlag)
|
||||
{
|
||||
ConcurrentQueue<InputRecord> queue = new ();
|
||||
|
||||
queue.Enqueue (
|
||||
new ()
|
||||
{
|
||||
EventType = WindowsConsole.EventType.Mouse,
|
||||
MouseEvent = new ()
|
||||
{
|
||||
MousePosition = new (32, 31),
|
||||
ButtonState = (ButtonState)wheelValue,
|
||||
ControlKeyState = ControlKeyState.NoControlKeyPressed,
|
||||
EventFlags = EventFlags.MouseWheeled
|
||||
}
|
||||
});
|
||||
|
||||
var processor = new WindowsInputProcessor (queue);
|
||||
|
||||
List<MouseEventArgs> mouseEvents = [];
|
||||
|
||||
processor.MouseEvent += (s, e) => { mouseEvents.Add (e); };
|
||||
|
||||
Assert.Empty (mouseEvents);
|
||||
|
||||
processor.ProcessQueue ();
|
||||
|
||||
MouseEventArgs s = Assert.Single (mouseEvents);
|
||||
Assert.Equal (s.Flags, expectedFlag);
|
||||
Assert.Equal (s.ScreenPosition, new (32, 31));
|
||||
}
|
||||
|
||||
public static IEnumerable<object []> MouseFlagTestData ()
|
||||
{
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button1Pressed),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button1Released),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.None)
|
||||
}
|
||||
];
|
||||
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (
|
||||
ButtonState.Button2Pressed,
|
||||
EventFlags.MouseMoved,
|
||||
ControlKeyState.NoControlKeyPressed,
|
||||
MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button2Released),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.None)
|
||||
}
|
||||
];
|
||||
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (
|
||||
ButtonState.Button3Pressed,
|
||||
EventFlags.MouseMoved,
|
||||
ControlKeyState.NoControlKeyPressed,
|
||||
MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button3Released),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.None)
|
||||
}
|
||||
];
|
||||
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (
|
||||
ButtonState.Button4Pressed,
|
||||
EventFlags.MouseMoved,
|
||||
ControlKeyState.NoControlKeyPressed,
|
||||
MouseFlags.Button4Pressed | MouseFlags.ReportMousePosition),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button4Released),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.MouseMoved, ControlKeyState.NoControlKeyPressed, MouseFlags.ReportMousePosition)
|
||||
}
|
||||
];
|
||||
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (
|
||||
ButtonState.RightmostButtonPressed,
|
||||
EventFlags.MouseMoved,
|
||||
ControlKeyState.NoControlKeyPressed,
|
||||
MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button3Released),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.None)
|
||||
}
|
||||
];
|
||||
|
||||
// Tests for holding down 2 buttons at once and releasing them one after the other
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (
|
||||
ButtonState.Button1Pressed | ButtonState.Button2Pressed,
|
||||
EventFlags.MouseMoved,
|
||||
ControlKeyState.NoControlKeyPressed,
|
||||
MouseFlags.Button1Pressed | MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
|
||||
Tuple.Create (
|
||||
ButtonState.Button1Pressed,
|
||||
EventFlags.NoEvent,
|
||||
ControlKeyState.NoControlKeyPressed,
|
||||
MouseFlags.Button1Pressed | MouseFlags.Button2Released),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button1Released),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.None)
|
||||
}
|
||||
];
|
||||
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (
|
||||
ButtonState.Button3Pressed | ButtonState.Button4Pressed,
|
||||
EventFlags.MouseMoved,
|
||||
ControlKeyState.NoControlKeyPressed,
|
||||
MouseFlags.Button3Pressed | MouseFlags.Button4Pressed | MouseFlags.ReportMousePosition),
|
||||
Tuple.Create (
|
||||
ButtonState.Button3Pressed,
|
||||
EventFlags.NoEvent,
|
||||
ControlKeyState.NoControlKeyPressed,
|
||||
MouseFlags.Button3Pressed | MouseFlags.Button4Released),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button3Released),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.None)
|
||||
}
|
||||
];
|
||||
|
||||
// Test for holding down 2 buttons at once and releasing them simultaneously
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (
|
||||
ButtonState.Button1Pressed | ButtonState.Button2Pressed,
|
||||
EventFlags.MouseMoved,
|
||||
ControlKeyState.NoControlKeyPressed,
|
||||
MouseFlags.Button1Pressed | MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
|
||||
Tuple.Create (
|
||||
ButtonState.NoButtonPressed,
|
||||
EventFlags.NoEvent,
|
||||
ControlKeyState.NoControlKeyPressed,
|
||||
MouseFlags.Button1Released | MouseFlags.Button2Released),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.None)
|
||||
}
|
||||
];
|
||||
|
||||
// Test that rightmost and button 3 are the same button so 2 states is still only 1 flag
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (
|
||||
ButtonState.Button3Pressed | ButtonState.RightmostButtonPressed,
|
||||
EventFlags.MouseMoved,
|
||||
ControlKeyState.NoControlKeyPressed,
|
||||
MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
|
||||
|
||||
// Can swap between without raising the released
|
||||
Tuple.Create (
|
||||
ButtonState.Button3Pressed,
|
||||
EventFlags.MouseMoved,
|
||||
ControlKeyState.NoControlKeyPressed,
|
||||
MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
|
||||
Tuple.Create (
|
||||
ButtonState.RightmostButtonPressed,
|
||||
EventFlags.MouseMoved,
|
||||
ControlKeyState.NoControlKeyPressed,
|
||||
MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
|
||||
|
||||
// Now with neither we get released
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.Button3Released),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NoControlKeyPressed, MouseFlags.None)
|
||||
}
|
||||
];
|
||||
|
||||
// Test for ControlKeyState buttons pressed and handled
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.LeftAltPressed, MouseFlags.Button1Pressed | MouseFlags.ButtonAlt),
|
||||
Tuple.Create (
|
||||
ButtonState.NoButtonPressed,
|
||||
EventFlags.NoEvent,
|
||||
ControlKeyState.LeftAltPressed,
|
||||
MouseFlags.Button1Released | MouseFlags.ButtonAlt),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.LeftAltPressed, MouseFlags.None | MouseFlags.ButtonAlt)
|
||||
}
|
||||
];
|
||||
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (
|
||||
ButtonState.Button1Pressed,
|
||||
EventFlags.NoEvent,
|
||||
ControlKeyState.RightAltPressed,
|
||||
MouseFlags.Button1Pressed | MouseFlags.ButtonAlt),
|
||||
Tuple.Create (
|
||||
ButtonState.NoButtonPressed,
|
||||
EventFlags.NoEvent,
|
||||
ControlKeyState.RightAltPressed,
|
||||
MouseFlags.Button1Released | MouseFlags.ButtonAlt),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.RightAltPressed, MouseFlags.None | MouseFlags.ButtonAlt)
|
||||
}
|
||||
];
|
||||
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (
|
||||
ButtonState.Button1Pressed,
|
||||
EventFlags.NoEvent,
|
||||
ControlKeyState.LeftControlPressed,
|
||||
MouseFlags.Button1Pressed | MouseFlags.ButtonCtrl),
|
||||
Tuple.Create (
|
||||
ButtonState.NoButtonPressed,
|
||||
EventFlags.NoEvent,
|
||||
ControlKeyState.LeftControlPressed,
|
||||
MouseFlags.Button1Released | MouseFlags.ButtonCtrl),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.LeftControlPressed, MouseFlags.None | MouseFlags.ButtonCtrl)
|
||||
}
|
||||
];
|
||||
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (
|
||||
ButtonState.Button1Pressed,
|
||||
EventFlags.NoEvent,
|
||||
ControlKeyState.RightControlPressed,
|
||||
MouseFlags.Button1Pressed | MouseFlags.ButtonCtrl),
|
||||
Tuple.Create (
|
||||
ButtonState.NoButtonPressed,
|
||||
EventFlags.NoEvent,
|
||||
ControlKeyState.RightControlPressed,
|
||||
MouseFlags.Button1Released | MouseFlags.ButtonCtrl),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.RightControlPressed, MouseFlags.None | MouseFlags.ButtonCtrl)
|
||||
}
|
||||
];
|
||||
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.ShiftPressed, MouseFlags.Button1Pressed | MouseFlags.ButtonShift),
|
||||
Tuple.Create (
|
||||
ButtonState.NoButtonPressed,
|
||||
EventFlags.NoEvent,
|
||||
ControlKeyState.ShiftPressed,
|
||||
MouseFlags.Button1Released | MouseFlags.ButtonShift),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.ShiftPressed, MouseFlags.None | MouseFlags.ButtonShift)
|
||||
}
|
||||
];
|
||||
|
||||
// Test for ControlKeyState buttons pressed and not handled
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.CapslockOn, MouseFlags.Button1Pressed),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.CapslockOn, MouseFlags.Button1Released),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.CapslockOn, MouseFlags.None)
|
||||
}
|
||||
];
|
||||
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.EnhancedKey, MouseFlags.Button1Pressed),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.EnhancedKey, MouseFlags.Button1Released),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.EnhancedKey, MouseFlags.None)
|
||||
}
|
||||
];
|
||||
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.NumlockOn, MouseFlags.Button1Pressed),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NumlockOn, MouseFlags.Button1Released),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.NumlockOn, MouseFlags.None)
|
||||
}
|
||||
];
|
||||
|
||||
yield return
|
||||
[
|
||||
new []
|
||||
{
|
||||
Tuple.Create (ButtonState.Button1Pressed, EventFlags.NoEvent, ControlKeyState.ScrolllockOn, MouseFlags.Button1Pressed),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.ScrolllockOn, MouseFlags.Button1Released),
|
||||
Tuple.Create (ButtonState.NoButtonPressed, EventFlags.NoEvent, ControlKeyState.ScrolllockOn, MouseFlags.None)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData (nameof (MouseFlagTestData))]
|
||||
internal void MouseFlags_Should_Map_Correctly (Tuple<ButtonState, EventFlags, ControlKeyState, MouseFlags> [] inputOutputPairs)
|
||||
{
|
||||
var processor = new WindowsInputProcessor (new ());
|
||||
|
||||
foreach (Tuple<ButtonState, EventFlags, ControlKeyState, MouseFlags> pair in inputOutputPairs)
|
||||
{
|
||||
var mockEvent = new MouseEventRecord { ButtonState = pair.Item1, EventFlags = pair.Item2, ControlKeyState = pair.Item3 };
|
||||
MouseEventArgs result = processor.ToMouseEvent (mockEvent);
|
||||
|
||||
Assert.Equal (pair.Item4, result.Flags);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
365
Tests/UnitTestsParallelizable/Input/EnqueueKeyEventTests.cs
Normal file
365
Tests/UnitTestsParallelizable/Input/EnqueueKeyEventTests.cs
Normal file
@@ -0,0 +1,365 @@
|
||||
#nullable enable
|
||||
using System.Collections.Concurrent;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
/// <summary>
|
||||
/// Parallelizable unit tests for IInput.EnqueueKeyDownEvent and InputProcessor.EnqueueKeyDownEvent.
|
||||
/// Tests validate the entire pipeline: Key → TInputRecord → Queue → ProcessQueue → Events.
|
||||
/// </summary>
|
||||
[Trait ("Category", "Input")]
|
||||
public class EnqueueKeyEventTests (ITestOutputHelper output)
|
||||
{
|
||||
private readonly ITestOutputHelper _output = output;
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Simulates the input thread by manually draining FakeInput's internal queue
|
||||
/// and moving items to the InputBuffer. This is needed because tests don't
|
||||
/// start the actual input thread via Run().
|
||||
/// </summary>
|
||||
private static void SimulateInputThread (FakeInput fakeInput, ConcurrentQueue<ConsoleKeyInfo> inputBuffer)
|
||||
{
|
||||
// FakeInput's Peek() checks _testInput
|
||||
while (fakeInput.Peek ())
|
||||
{
|
||||
// Read() drains _testInput and returns items
|
||||
foreach (ConsoleKeyInfo item in fakeInput.Read ())
|
||||
{
|
||||
// Manually add to InputBuffer (simulating what Run() would do)
|
||||
inputBuffer.Enqueue (item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes the input queue with support for keys that may be held by the ANSI parser (like Esc).
|
||||
/// The parser holds Esc for 50ms waiting to see if it's part of an escape sequence.
|
||||
/// </summary>
|
||||
private static void ProcessQueueWithEscapeHandling (FakeInputProcessor processor, int maxAttempts = 3)
|
||||
{
|
||||
// First attempt - process immediately
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// For escape sequences, we may need to wait and process again
|
||||
// The parser holds escape for 50ms before releasing
|
||||
for (var attempt = 1; attempt < maxAttempts; attempt++)
|
||||
{
|
||||
Thread.Sleep (60); // Wait longer than the 50ms escape timeout
|
||||
processor.ProcessQueue (); // This should release any held escape keys
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FakeInput EnqueueKeyDownEvent Tests
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueKeyDownEvent_AddsSingleKeyToQueue ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
List<Key> receivedKeys = [];
|
||||
processor.KeyDown += (_, k) => receivedKeys.Add (k);
|
||||
|
||||
Key key = Key.A;
|
||||
|
||||
// Act
|
||||
processor.EnqueueKeyDownEvent (key);
|
||||
|
||||
// Simulate the input thread moving items from _testInput to InputBuffer
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert - Verify the key made it through
|
||||
Assert.Single (receivedKeys);
|
||||
Assert.Equal (key, receivedKeys [0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueKeyDownEvent_SupportsMultipleKeys ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
Key [] keys = [Key.A, Key.B, Key.C, Key.Enter];
|
||||
List<Key> receivedKeys = [];
|
||||
processor.KeyDown += (_, k) => receivedKeys.Add (k);
|
||||
|
||||
// Act
|
||||
foreach (Key key in keys)
|
||||
{
|
||||
processor.EnqueueKeyDownEvent (key);
|
||||
}
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.Equal (keys.Length, receivedKeys.Count);
|
||||
Assert.Equal (keys, receivedKeys);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (KeyCode.A, false, false, false)]
|
||||
[InlineData (KeyCode.A, true, false, false)] // Shift+A
|
||||
[InlineData (KeyCode.A, false, true, false)] // Ctrl+A
|
||||
[InlineData (KeyCode.A, false, false, true)] // Alt+A
|
||||
[InlineData (KeyCode.A, true, true, true)] // Ctrl+Shift+Alt+A
|
||||
public void FakeInput_EnqueueKeyDownEvent_PreservesModifiers (KeyCode keyCode, bool shift, bool ctrl, bool alt)
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
var key = new Key (keyCode);
|
||||
|
||||
if (shift)
|
||||
{
|
||||
key = key.WithShift;
|
||||
}
|
||||
|
||||
if (ctrl)
|
||||
{
|
||||
key = key.WithCtrl;
|
||||
}
|
||||
|
||||
if (alt)
|
||||
{
|
||||
key = key.WithAlt;
|
||||
}
|
||||
|
||||
Key? receivedKey = null;
|
||||
processor.KeyDown += (_, k) => receivedKey = k;
|
||||
|
||||
// Act
|
||||
processor.EnqueueKeyDownEvent (key);
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull (receivedKey);
|
||||
Assert.Equal (key.IsShift, receivedKey.IsShift);
|
||||
Assert.Equal (key.IsCtrl, receivedKey.IsCtrl);
|
||||
Assert.Equal (key.IsAlt, receivedKey.IsAlt);
|
||||
Assert.Equal (key.KeyCode, receivedKey.KeyCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (KeyCode.Enter)]
|
||||
[InlineData (KeyCode.Tab)]
|
||||
[InlineData (KeyCode.Esc)]
|
||||
[InlineData (KeyCode.Backspace)]
|
||||
[InlineData (KeyCode.Delete)]
|
||||
[InlineData (KeyCode.CursorUp)]
|
||||
[InlineData (KeyCode.CursorDown)]
|
||||
[InlineData (KeyCode.CursorLeft)]
|
||||
[InlineData (KeyCode.CursorRight)]
|
||||
[InlineData (KeyCode.F1)]
|
||||
[InlineData (KeyCode.F12)]
|
||||
public void FakeInput_EnqueueKeyDownEvent_SupportsSpecialKeys (KeyCode keyCode)
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
var key = new Key (keyCode);
|
||||
Key? receivedKey = null;
|
||||
processor.KeyDown += (_, k) => receivedKey = k;
|
||||
|
||||
// Act
|
||||
processor.EnqueueKeyDownEvent (key);
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
|
||||
// Esc is special - the ANSI parser holds it waiting for potential escape sequences
|
||||
// We need to process with delay to let the parser release it after timeout
|
||||
if (keyCode == KeyCode.Esc)
|
||||
{
|
||||
ProcessQueueWithEscapeHandling (processor);
|
||||
}
|
||||
else
|
||||
{
|
||||
processor.ProcessQueue ();
|
||||
}
|
||||
|
||||
// Assert
|
||||
Assert.NotNull (receivedKey);
|
||||
Assert.Equal (key.KeyCode, receivedKey.KeyCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueKeyDownEvent_RaisesKeyDownAndKeyUpEvents ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
var keyDownCount = 0;
|
||||
var keyUpCount = 0;
|
||||
processor.KeyDown += (_, _) => keyDownCount++;
|
||||
processor.KeyUp += (_, _) => keyUpCount++;
|
||||
|
||||
// Act
|
||||
processor.EnqueueKeyDownEvent (Key.A);
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert - FakeDriver simulates KeyUp immediately after KeyDown
|
||||
Assert.Equal (1, keyDownCount);
|
||||
Assert.Equal (1, keyUpCount);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region InputProcessor Pipeline Tests
|
||||
|
||||
[Fact]
|
||||
public void InputProcessor_EnqueueKeyDownEvent_RequiresTestableInput ()
|
||||
{
|
||||
// Arrange
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
|
||||
// Don't set InputImpl (or set to non-testable)
|
||||
|
||||
// Act & Assert - Should not throw, but also won't add to queue
|
||||
// (because InputImpl is null or not ITestableInput)
|
||||
processor.EnqueueKeyDownEvent (Key.A);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// No events should be raised since no input was added
|
||||
var eventRaised = false;
|
||||
processor.KeyDown += (_, _) => eventRaised = true;
|
||||
processor.ProcessQueue ();
|
||||
Assert.False (eventRaised);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InputProcessor_ProcessQueue_DrainsPendingInputRecords ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
List<Key> receivedKeys = [];
|
||||
processor.KeyDown += (_, k) => receivedKeys.Add (k);
|
||||
|
||||
// Act - Enqueue multiple keys before processing
|
||||
processor.EnqueueKeyDownEvent (Key.A);
|
||||
processor.EnqueueKeyDownEvent (Key.B);
|
||||
processor.EnqueueKeyDownEvent (Key.C);
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert - After processing, queue should be empty and all keys received
|
||||
Assert.Empty (queue);
|
||||
Assert.Equal (3, receivedKeys.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Thread Safety Tests
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueKeyDownEvent_IsThreadSafe ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
ConcurrentBag<Key> receivedKeys = [];
|
||||
processor.KeyDown += (_, k) => receivedKeys.Add (k);
|
||||
|
||||
const int threadCount = 10;
|
||||
const int keysPerThread = 100;
|
||||
Thread [] threads = new Thread [threadCount];
|
||||
|
||||
// Act - Enqueue keys from multiple threads
|
||||
for (var t = 0; t < threadCount; t++)
|
||||
{
|
||||
threads [t] = new (() =>
|
||||
{
|
||||
for (var i = 0; i < keysPerThread; i++)
|
||||
{
|
||||
processor.EnqueueKeyDownEvent (Key.A);
|
||||
}
|
||||
});
|
||||
threads [t].Start ();
|
||||
}
|
||||
|
||||
// Wait for all threads to complete
|
||||
foreach (Thread thread in threads)
|
||||
{
|
||||
thread.Join ();
|
||||
}
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.Equal (threadCount * keysPerThread, receivedKeys.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Error Handling Tests
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueKeyDownEvent_WithInvalidKey_DoesNotThrow ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
// Act & Assert - Empty/null key should not throw
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
processor.EnqueueKeyDownEvent (Key.Empty);
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
});
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
531
Tests/UnitTestsParallelizable/Input/EnqueueMouseEventTests.cs
Normal file
531
Tests/UnitTestsParallelizable/Input/EnqueueMouseEventTests.cs
Normal file
@@ -0,0 +1,531 @@
|
||||
#nullable enable
|
||||
using System.Collections.Concurrent;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace UnitTests_Parallelizable.DriverTests;
|
||||
|
||||
/// <summary>
|
||||
/// Parallelizable unit tests for IInputProcessor.EnqueueMouseEvent.
|
||||
/// Tests validate the entire pipeline: MouseEventArgs → TInputRecord → Queue → ProcessQueue → Events.
|
||||
/// fully implemented in InputProcessorImpl (base class). Only WindowsInputProcessor has a working implementation.
|
||||
/// </summary>
|
||||
[Trait ("Category", "Input")]
|
||||
public class EnqueueMouseEventTests (ITestOutputHelper output)
|
||||
{
|
||||
private readonly ITestOutputHelper _output = output;
|
||||
|
||||
#region Mouse Event Sequencing Tests
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueMouseEvent_HandlesCompleteClickSequence ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
List<MouseEventArgs> receivedEvents = [];
|
||||
processor.MouseEvent += (_, e) => receivedEvents.Add (e);
|
||||
|
||||
// Act - Simulate a complete click: press → release → click
|
||||
processor.EnqueueMouseEvent (
|
||||
new()
|
||||
{
|
||||
Position = new (10, 5),
|
||||
Flags = MouseFlags.Button1Pressed
|
||||
});
|
||||
|
||||
processor.EnqueueMouseEvent (
|
||||
new()
|
||||
{
|
||||
Position = new (10, 5),
|
||||
Flags = MouseFlags.Button1Released
|
||||
});
|
||||
|
||||
// The MouseInterpreter in the processor should generate a clicked event
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
// We should see at least the pressed and released events
|
||||
Assert.True (receivedEvents.Count >= 2);
|
||||
Assert.Contains (receivedEvents, e => e.Flags.HasFlag (MouseFlags.Button1Pressed));
|
||||
Assert.Contains (receivedEvents, e => e.Flags.HasFlag (MouseFlags.Button1Released));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Thread Safety Tests
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueMouseEvent_IsThreadSafe ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
ConcurrentBag<MouseEventArgs> receivedEvents = [];
|
||||
processor.MouseEvent += (_, e) => receivedEvents.Add (e);
|
||||
|
||||
const int threadCount = 10;
|
||||
const int eventsPerThread = 100;
|
||||
Thread [] threads = new Thread [threadCount];
|
||||
|
||||
// Act - Enqueue mouse events from multiple threads
|
||||
for (var t = 0; t < threadCount; t++)
|
||||
{
|
||||
int threadId = t;
|
||||
|
||||
threads [t] = new (() =>
|
||||
{
|
||||
for (var i = 0; i < eventsPerThread; i++)
|
||||
{
|
||||
processor.EnqueueMouseEvent (
|
||||
new()
|
||||
{
|
||||
Position = new (threadId, i),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
});
|
||||
}
|
||||
});
|
||||
threads [t].Start ();
|
||||
}
|
||||
|
||||
// Wait for all threads to complete
|
||||
foreach (Thread thread in threads)
|
||||
{
|
||||
thread.Join ();
|
||||
}
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.Equal (threadCount * eventsPerThread, receivedEvents.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Simulates the input thread by manually draining FakeInput's internal queue
|
||||
/// and moving items to the InputBuffer. This is needed because tests don't
|
||||
/// start the actual input thread via Run().
|
||||
/// </summary>
|
||||
private static void SimulateInputThread (FakeInput fakeInput, ConcurrentQueue<ConsoleKeyInfo> inputBuffer)
|
||||
{
|
||||
// FakeInput's Peek() checks _testInput
|
||||
while (fakeInput.Peek ())
|
||||
{
|
||||
// Read() drains _testInput and returns items
|
||||
foreach (ConsoleKeyInfo item in fakeInput.Read ())
|
||||
{
|
||||
// Manually add to InputBuffer (simulating what Run() would do)
|
||||
inputBuffer.Enqueue (item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region FakeInputProcessor EnqueueMouseEvent Tests
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueMouseEvent_AddsSingleMouseEventToQueue ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
List<MouseEventArgs> receivedEvents = [];
|
||||
processor.MouseEvent += (_, e) => receivedEvents.Add (e);
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new (10, 5),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
};
|
||||
|
||||
// Act
|
||||
processor.EnqueueMouseEvent (mouseEvent);
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert - Verify the mouse event made it through
|
||||
Assert.Single (receivedEvents);
|
||||
Assert.Equal (mouseEvent.Position, receivedEvents [0].Position);
|
||||
Assert.Equal (mouseEvent.Flags, receivedEvents [0].Flags);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueMouseEvent_SupportsMultipleEvents ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
MouseEventArgs [] events =
|
||||
[
|
||||
new () { Position = new (10, 5), Flags = MouseFlags.Button1Pressed },
|
||||
new () { Position = new (10, 5), Flags = MouseFlags.Button1Released },
|
||||
new () { Position = new (15, 8), Flags = MouseFlags.ReportMousePosition },
|
||||
new () { Position = new (20, 10), Flags = MouseFlags.Button1Clicked }
|
||||
];
|
||||
|
||||
List<MouseEventArgs> receivedEvents = [];
|
||||
processor.MouseEvent += (_, e) => receivedEvents.Add (e);
|
||||
|
||||
// Act
|
||||
foreach (MouseEventArgs mouseEvent in events)
|
||||
{
|
||||
processor.EnqueueMouseEvent (mouseEvent);
|
||||
}
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
// The MouseInterpreter processes Button1Pressed followed by Button1Released and generates
|
||||
// an additional Button1Clicked event, so we expect 5 events total:
|
||||
// 1. Button1Pressed (original)
|
||||
// 2. Button1Released (original)
|
||||
// 3. Button1Clicked (generated by MouseInterpreter from press+release)
|
||||
// 4. ReportMousePosition (original)
|
||||
// 5. Button1Clicked (original)
|
||||
Assert.Equal (5, receivedEvents.Count);
|
||||
|
||||
// Verify the original events are present
|
||||
Assert.Contains (receivedEvents, e => e.Flags == MouseFlags.Button1Pressed && e.Position == new Point (10, 5));
|
||||
Assert.Contains (receivedEvents, e => e.Flags == MouseFlags.Button1Released && e.Position == new Point (10, 5));
|
||||
Assert.Contains (receivedEvents, e => e.Flags == MouseFlags.ReportMousePosition && e.Position == new Point (15, 8));
|
||||
|
||||
// There should be two clicked events: one generated, one original
|
||||
var clickedEvents = receivedEvents.Where (e => e.Flags == MouseFlags.Button1Clicked).ToList ();
|
||||
Assert.Equal (2, clickedEvents.Count);
|
||||
Assert.Contains (clickedEvents, e => e.Position == new Point (10, 5)); // Generated from press+release
|
||||
Assert.Contains (clickedEvents, e => e.Position == new Point (20, 10)); // Original
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (MouseFlags.Button1Clicked)]
|
||||
[InlineData (MouseFlags.Button2Clicked)]
|
||||
[InlineData (MouseFlags.Button3Clicked)]
|
||||
[InlineData (MouseFlags.Button4Clicked)]
|
||||
[InlineData (MouseFlags.Button1DoubleClicked)]
|
||||
[InlineData (MouseFlags.Button1TripleClicked)]
|
||||
public void FakeInput_EnqueueMouseEvent_SupportsAllButtonClicks (MouseFlags flags)
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new (10, 5),
|
||||
Flags = flags
|
||||
};
|
||||
|
||||
MouseEventArgs? receivedEvent = null;
|
||||
processor.MouseEvent += (_, e) => receivedEvent = e;
|
||||
|
||||
// Act
|
||||
processor.EnqueueMouseEvent (mouseEvent);
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull (receivedEvent);
|
||||
Assert.Equal (flags, receivedEvent.Flags);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (0, 0)]
|
||||
[InlineData (10, 5)]
|
||||
[InlineData (79, 24)] // Near screen edge (assuming 80x25)
|
||||
[InlineData (100, 100)] // Beyond typical screen
|
||||
public void FakeInput_EnqueueMouseEvent_PreservesPosition (int x, int y)
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new (x, y),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
};
|
||||
|
||||
MouseEventArgs? receivedEvent = null;
|
||||
processor.MouseEvent += (_, e) => receivedEvent = e;
|
||||
|
||||
// Act
|
||||
processor.EnqueueMouseEvent (mouseEvent);
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull (receivedEvent);
|
||||
Assert.Equal (x, receivedEvent.Position.X);
|
||||
Assert.Equal (y, receivedEvent.Position.Y);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (MouseFlags.ButtonShift)]
|
||||
[InlineData (MouseFlags.ButtonCtrl)]
|
||||
[InlineData (MouseFlags.ButtonAlt)]
|
||||
[InlineData (MouseFlags.ButtonShift | MouseFlags.ButtonCtrl)]
|
||||
[InlineData (MouseFlags.ButtonShift | MouseFlags.ButtonAlt)]
|
||||
[InlineData (MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt)]
|
||||
[InlineData (MouseFlags.ButtonShift | MouseFlags.ButtonCtrl | MouseFlags.ButtonAlt)]
|
||||
public void FakeInput_EnqueueMouseEvent_PreservesModifiers (MouseFlags modifiers)
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new (10, 5),
|
||||
Flags = MouseFlags.Button1Clicked | modifiers
|
||||
};
|
||||
|
||||
MouseEventArgs? receivedEvent = null;
|
||||
processor.MouseEvent += (_, e) => receivedEvent = e;
|
||||
|
||||
// Act
|
||||
processor.EnqueueMouseEvent (mouseEvent);
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull (receivedEvent);
|
||||
Assert.True (receivedEvent.Flags.HasFlag (MouseFlags.Button1Clicked));
|
||||
|
||||
if (modifiers.HasFlag (MouseFlags.ButtonShift))
|
||||
{
|
||||
Assert.True (receivedEvent.Flags.HasFlag (MouseFlags.ButtonShift));
|
||||
}
|
||||
|
||||
if (modifiers.HasFlag (MouseFlags.ButtonCtrl))
|
||||
{
|
||||
Assert.True (receivedEvent.Flags.HasFlag (MouseFlags.ButtonCtrl));
|
||||
}
|
||||
|
||||
if (modifiers.HasFlag (MouseFlags.ButtonAlt))
|
||||
{
|
||||
Assert.True (receivedEvent.Flags.HasFlag (MouseFlags.ButtonAlt));
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (MouseFlags.WheeledUp)]
|
||||
[InlineData (MouseFlags.WheeledDown)]
|
||||
[InlineData (MouseFlags.WheeledLeft)]
|
||||
[InlineData (MouseFlags.WheeledRight)]
|
||||
public void FakeInput_EnqueueMouseEvent_SupportsMouseWheel (MouseFlags wheelFlag)
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
MouseEventArgs mouseEvent = new ()
|
||||
{
|
||||
Position = new (10, 5),
|
||||
Flags = wheelFlag
|
||||
};
|
||||
|
||||
MouseEventArgs? receivedEvent = null;
|
||||
processor.MouseEvent += (_, e) => receivedEvent = e;
|
||||
|
||||
// Act
|
||||
processor.EnqueueMouseEvent (mouseEvent);
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull (receivedEvent);
|
||||
Assert.True (receivedEvent.Flags.HasFlag (wheelFlag));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueMouseEvent_SupportsMouseMove ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
List<MouseEventArgs> receivedEvents = [];
|
||||
processor.MouseEvent += (_, e) => receivedEvents.Add (e);
|
||||
|
||||
MouseEventArgs [] events =
|
||||
[
|
||||
new () { Position = new (0, 0), Flags = MouseFlags.ReportMousePosition },
|
||||
new () { Position = new (5, 5), Flags = MouseFlags.ReportMousePosition },
|
||||
new () { Position = new (10, 10), Flags = MouseFlags.ReportMousePosition }
|
||||
];
|
||||
|
||||
// Act
|
||||
foreach (MouseEventArgs mouseEvent in events)
|
||||
{
|
||||
processor.EnqueueMouseEvent (mouseEvent);
|
||||
}
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert
|
||||
Assert.Equal (3, receivedEvents.Count);
|
||||
Assert.Equal (new (0, 0), receivedEvents [0].Position);
|
||||
Assert.Equal (new (5, 5), receivedEvents [1].Position);
|
||||
Assert.Equal (new (10, 10), receivedEvents [2].Position);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region InputProcessor Pipeline Tests
|
||||
|
||||
[Fact]
|
||||
public void InputProcessor_EnqueueMouseEvent_DoesNotThrow ()
|
||||
{
|
||||
// Arrange
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
|
||||
// Don't set InputImpl (or set to non-testable)
|
||||
|
||||
// Act & Assert - Should not throw even if not implemented
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
processor.EnqueueMouseEvent (
|
||||
new()
|
||||
{
|
||||
Position = new (10, 5),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
});
|
||||
processor.ProcessQueue ();
|
||||
});
|
||||
|
||||
// The base implementation logs a critical message but doesn't throw
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InputProcessor_ProcessQueue_DrainsPendingMouseEvents ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
List<MouseEventArgs> receivedEvents = [];
|
||||
processor.MouseEvent += (_, e) => receivedEvents.Add (e);
|
||||
|
||||
// Act - Enqueue multiple events before processing
|
||||
processor.EnqueueMouseEvent (new() { Position = new (1, 1), Flags = MouseFlags.Button1Pressed });
|
||||
processor.EnqueueMouseEvent (new() { Position = new (2, 2), Flags = MouseFlags.ReportMousePosition });
|
||||
processor.EnqueueMouseEvent (new() { Position = new (3, 3), Flags = MouseFlags.Button1Released });
|
||||
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
|
||||
// Assert - After processing, all events should be received
|
||||
Assert.Empty (queue);
|
||||
Assert.Equal (3, receivedEvents.Count);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Error Handling Tests
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueMouseEvent_WithInvalidEvent_DoesNotThrow ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
// Act & Assert - Empty/default mouse event should not throw
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
processor.EnqueueMouseEvent (new ());
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
});
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FakeInput_EnqueueMouseEvent_WithNegativePosition_DoesNotThrow ()
|
||||
{
|
||||
// Arrange
|
||||
var fakeInput = new FakeInput ();
|
||||
ConcurrentQueue<ConsoleKeyInfo> queue = new ();
|
||||
fakeInput.Initialize (queue);
|
||||
|
||||
var processor = new FakeInputProcessor (queue);
|
||||
processor.InputImpl = fakeInput;
|
||||
|
||||
// Act & Assert - Negative positions should not throw
|
||||
Exception? exception = Record.Exception (() =>
|
||||
{
|
||||
processor.EnqueueMouseEvent (
|
||||
new()
|
||||
{
|
||||
Position = new (-10, -5),
|
||||
Flags = MouseFlags.Button1Clicked
|
||||
});
|
||||
SimulateInputThread (fakeInput, queue);
|
||||
processor.ProcessQueue ();
|
||||
});
|
||||
|
||||
Assert.Null (exception);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -591,4 +591,20 @@ public class KeyTests
|
||||
r.Dispose ();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Set_Key_Separator_With_Rune_Default_Ensure_Using_The_Default_Plus ()
|
||||
{
|
||||
Key key = new (Key.A.WithCtrl);
|
||||
Assert.Equal ((Rune)'+', Key.Separator);
|
||||
Assert.Equal ("Ctrl+A", key.ToString ());
|
||||
|
||||
// NOTE: This means this test can't be parallelized
|
||||
Key.Separator = new ('-');
|
||||
Assert.Equal ((Rune)'-', Key.Separator);
|
||||
Assert.Equal ("Ctrl-A", key.ToString ());
|
||||
|
||||
Key.Separator = new ();
|
||||
Assert.Equal ((Rune)'+', Key.Separator);
|
||||
Assert.Equal ("Ctrl+A", key.ToString ());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
|
||||
using TerminalGuiFluentTesting;
|
||||
|
||||
namespace UnitTests.Parallelizable;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for parallelizable tests. Ensures that tests can run in parallel without interference
|
||||
/// by setting various Terminal.Gui static properties to their default values. E.g. View.EnableDebugIDisposableAsserts.
|
||||
/// </summary>
|
||||
[Collection ("Global Test Setup")]
|
||||
public abstract class ParallelizableBase
|
||||
{
|
||||
// Common setup or utilities for all tests can go here
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new FakeDriver instance with the specified buffer size.
|
||||
/// This is a convenience method for tests that need to use Draw() and DriverAssert
|
||||
/// without relying on Application.Driver.
|
||||
/// </summary>
|
||||
/// <param name="width">Width of the driver buffer</param>
|
||||
/// <param name="height">Height of the driver buffer</param>
|
||||
/// <returns>A configured IFakeConsoleDriver instance</returns>
|
||||
protected static IConsoleDriver CreateFakeDriver (int width = 25, int height = 25)
|
||||
{
|
||||
IConsoleDriver driver = new FakeDriver ();
|
||||
driver.SetScreenSize (width, height);
|
||||
return driver;
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,11 @@ using System.Collections;
|
||||
using System.Globalization;
|
||||
using System.Resources;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnitTests;
|
||||
|
||||
namespace UnitTests_Parallelizable.ResourcesTests;
|
||||
|
||||
public class ResourceManagerTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
public class ResourceManagerTests : FakeDriverBase
|
||||
{
|
||||
private const string EXISTENT_CULTURE = "pt-PT";
|
||||
private const string NO_EXISTENT_CULTURE = "de-DE";
|
||||
|
||||
@@ -47,7 +47,7 @@ public class GlobalTestSetup : IDisposable
|
||||
// Don't check Application.Force16Colors
|
||||
//Assert.False (Application.Force16Colors);
|
||||
Assert.Null (Application.Driver);
|
||||
Assert.False (Application.EndAfterFirstIteration);
|
||||
Assert.False (Application.StopAfterFirstIteration);
|
||||
Assert.Equal (Key.Tab.WithShift, Application.PrevTabKey);
|
||||
Assert.Equal (Key.Tab, Application.NextTabKey);
|
||||
Assert.Equal (Key.F6.WithShift, Application.PrevTabGroupKey);
|
||||
@@ -58,8 +58,7 @@ public class GlobalTestSetup : IDisposable
|
||||
Assert.False (Application.Initialized);
|
||||
Assert.Equal (Application.GetSupportedCultures (), Application.SupportedCultures);
|
||||
Assert.Equal (Application.GetAvailableCulturesFromEmbeddedResources (), Application.SupportedCultures);
|
||||
Assert.False (Application._forceFakeConsole);
|
||||
Assert.Equal (-1, Application.MainThreadId);
|
||||
Assert.Null (Application.MainThreadId);
|
||||
Assert.Empty (Application.TopLevels);
|
||||
Assert.Empty (Application.CachedViewsUnderMouse);
|
||||
|
||||
@@ -74,9 +73,9 @@ public class GlobalTestSetup : IDisposable
|
||||
Assert.Null (Application.Popover);
|
||||
|
||||
// Events - Can't check
|
||||
//Assert.Null (Application.NotifyNewRunState);
|
||||
//Assert.Null (Application.NotifyNewRunState);
|
||||
//Assert.Null (Application.Iteration);
|
||||
//Assert.Null (Application.SessionBegun);
|
||||
//Assert.Null (Application.SessionBegun);
|
||||
//Assert.Null (ApplicationImpl.Instance.Iteration);
|
||||
//Assert.Null (Application.SizeChanging);
|
||||
//Assert.Null (Application.GrabbedMouse);
|
||||
//Assert.Null (Application.UnGrabbingMouse);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using TerminalGuiFluentTesting;
|
||||
using UnitTests;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
@@ -9,14 +7,10 @@ namespace UnitTests_Parallelizable.TextTests;
|
||||
/// Pure unit tests for Autocomplete functionality that don't require Application or Driver.
|
||||
/// Integration tests for Autocomplete (popup behavior, rendering) remain in UnitTests.
|
||||
/// </summary>
|
||||
public class AutocompleteTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
public class AutocompleteTests (ITestOutputHelper output) : FakeDriverBase
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public AutocompleteTests (ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
private readonly ITestOutputHelper _output = output;
|
||||
|
||||
[Fact]
|
||||
public void Test_GenerateSuggestions_Simple ()
|
||||
{
|
||||
|
||||
669
Tests/UnitTestsParallelizable/Text/TextFormatterDrawTests.cs
Normal file
669
Tests/UnitTestsParallelizable/Text/TextFormatterDrawTests.cs
Normal file
@@ -0,0 +1,669 @@
|
||||
#nullable enable
|
||||
using System.Text;
|
||||
using UICatalog;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
// Alias Console to MockConsole so we don't accidentally use Console
|
||||
|
||||
namespace UnitTests.TextTests;
|
||||
|
||||
public class TextFormatterDrawTests (ITestOutputHelper output) : FakeDriverBase
|
||||
{
|
||||
public static IEnumerable<object []> CMGlyphs =>
|
||||
new List<object []> { new object [] { $"{Glyphs.LeftBracket} Say Hello 你 {Glyphs.RightBracket}", 16, 15 } };
|
||||
|
||||
[Theory]
|
||||
[InlineData ("A", 1, 0, "")]
|
||||
[InlineData ("A", 0, 1, "")]
|
||||
[InlineData ("AB1 2", 2, 1, "2")]
|
||||
[InlineData ("AB12", 5, 1, "21BA")]
|
||||
[InlineData ("AB\n12", 5, 2, "21\nBA")]
|
||||
[InlineData ("ABC 123 456", 7, 2, "CBA \n654 321")]
|
||||
[InlineData ("こんにちは", 1, 1, "")]
|
||||
[InlineData ("こんにちは", 2, 1, "は")]
|
||||
[InlineData ("こんにちは", 5, 1, "はち")]
|
||||
[InlineData ("こんにちは", 10, 1, "はちにんこ")]
|
||||
[InlineData ("こんにちは\nAB\n12", 10, 3, "21 \nBA \nはちにんこ")]
|
||||
public void Draw_Horizontal_RightLeft_BottomTop (string text, int width, int height, string expectedText)
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Text = text,
|
||||
Direction = TextDirection.RightLeft_BottomTop
|
||||
};
|
||||
|
||||
tf.ConstrainToWidth = width;
|
||||
tf.ConstrainToHeight = height;
|
||||
tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default, driver: driver);
|
||||
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData ("A", 1, 0, "")]
|
||||
[InlineData ("A", 0, 1, "")]
|
||||
[InlineData ("AB1 2", 2, 1, "2")]
|
||||
[InlineData ("AB12", 5, 1, "21BA")]
|
||||
[InlineData ("AB\n12", 5, 2, "BA\n21")]
|
||||
[InlineData ("ABC 123 456", 7, 2, "654 321\nCBA ")]
|
||||
[InlineData ("こんにちは", 1, 1, "")]
|
||||
[InlineData ("こんにちは", 2, 1, "は")]
|
||||
[InlineData ("こんにちは", 5, 1, "はち")]
|
||||
[InlineData ("こんにちは", 10, 1, "はちにんこ")]
|
||||
[InlineData ("こんにちは\nAB\n12", 10, 3, "はちにんこ\nBA \n21 ")]
|
||||
public void Draw_Horizontal_RightLeft_TopBottom (string text, int width, int height, string expectedText)
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Text = text,
|
||||
Direction = TextDirection.RightLeft_TopBottom
|
||||
};
|
||||
|
||||
tf.ConstrainToWidth = width;
|
||||
tf.ConstrainToHeight = height;
|
||||
tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default, driver: driver);
|
||||
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData ("A", 0, 1, "", 0)]
|
||||
[InlineData ("A", 1, 1, "A", 0)]
|
||||
[InlineData ("A", 2, 2, " A", 1)]
|
||||
[InlineData ("AB", 1, 1, "B", 0)]
|
||||
[InlineData ("AB", 2, 2, " A\n B", 0)]
|
||||
[InlineData ("ABC", 3, 2, " B\n C", 0)]
|
||||
[InlineData ("ABC", 4, 2, " B\n C", 0)]
|
||||
[InlineData ("ABC", 6, 2, " B\n C", 0)]
|
||||
[InlineData ("こんにちは", 0, 1, "", 0)]
|
||||
[InlineData ("こんにちは", 1, 0, "", 0)]
|
||||
[InlineData ("こんにちは", 1, 1, "", 0)]
|
||||
[InlineData ("こんにちは", 2, 1, "は", 0)]
|
||||
[InlineData ("こんにちは", 2, 2, "ち\nは", 0)]
|
||||
[InlineData ("こんにちは", 2, 3, "に\nち\nは", 0)]
|
||||
[InlineData ("こんにちは", 2, 4, "ん\nに\nち\nは", 0)]
|
||||
[InlineData ("こんにちは", 2, 5, "こ\nん\nに\nち\nは", 0)]
|
||||
[InlineData ("こんにちは", 2, 6, "こ\nん\nに\nち\nは", 1)]
|
||||
[InlineData ("ABCD\nこんにちは", 4, 7, " こ\n Aん\n Bに\n Cち\n Dは", 2)]
|
||||
[InlineData ("こんにちは\nABCD", 3, 7, "こ \nんA\nにB\nちC\nはD", 2)]
|
||||
public void Draw_Vertical_Bottom_Horizontal_Right (string text, int width, int height, string expectedText, int expectedY)
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Text = text,
|
||||
Alignment = Alignment.End,
|
||||
Direction = TextDirection.TopBottom_LeftRight,
|
||||
VerticalAlignment = Alignment.End
|
||||
};
|
||||
|
||||
tf.ConstrainToWidth = width;
|
||||
tf.ConstrainToHeight = height;
|
||||
|
||||
tf.Draw (new (Point.Empty, new (width, height)), Attribute.Default, Attribute.Default, driver: driver);
|
||||
Rectangle rect = DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver);
|
||||
Assert.Equal (expectedY, rect.Y);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData ("A", 1, 0, "")]
|
||||
[InlineData ("A", 0, 1, "")]
|
||||
[InlineData ("AB1 2", 1, 2, "2")]
|
||||
[InlineData ("AB12", 1, 5, "2\n1\nB\nA")]
|
||||
[InlineData ("AB\n12", 2, 5, "B2\nA1")]
|
||||
[InlineData ("ABC 123 456", 2, 7, "6C\n5B\n4A\n \n3 \n2 \n1 ")]
|
||||
[InlineData ("こんにちは", 1, 1, "")]
|
||||
[InlineData ("こんにちは", 2, 1, "は")]
|
||||
[InlineData ("こんにちは", 2, 5, "は\nち\nに\nん\nこ")]
|
||||
[InlineData ("こんにちは", 2, 10, "は\nち\nに\nん\nこ")]
|
||||
[InlineData ("こんにちは\nAB\n12", 4, 10, "はB2\nちA1\nに \nん \nこ ")]
|
||||
public void Draw_Vertical_BottomTop_LeftRight (string text, int width, int height, string expectedText)
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Text = text,
|
||||
Direction = TextDirection.BottomTop_LeftRight
|
||||
};
|
||||
|
||||
tf.ConstrainToWidth = width;
|
||||
tf.ConstrainToHeight = height;
|
||||
tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default, driver: driver);
|
||||
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData ("A", 1, 0, "")]
|
||||
[InlineData ("A", 0, 1, "")]
|
||||
[InlineData ("AB1 2", 1, 2, "2")]
|
||||
[InlineData ("AB12", 1, 5, "2\n1\nB\nA")]
|
||||
[InlineData ("AB\n12", 2, 5, "2B\n1A")]
|
||||
[InlineData ("ABC 123 456", 2, 7, "C6\nB5\nA4\n \n 3\n 2\n 1")]
|
||||
[InlineData ("こんにちは", 1, 1, "")]
|
||||
[InlineData ("こんにちは", 2, 1, "は")]
|
||||
[InlineData ("こんにちは", 2, 5, "は\nち\nに\nん\nこ")]
|
||||
[InlineData ("こんにちは", 2, 10, "は\nち\nに\nん\nこ")]
|
||||
[InlineData ("こんにちは\nAB\n12", 4, 10, "2Bは\n1Aち\n に\n ん\n こ")]
|
||||
public void Draw_Vertical_BottomTop_RightLeft (string text, int width, int height, string expectedText)
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Text = text,
|
||||
Direction = TextDirection.BottomTop_RightLeft
|
||||
};
|
||||
|
||||
tf.ConstrainToWidth = width;
|
||||
tf.ConstrainToHeight = height;
|
||||
tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default, driver: driver);
|
||||
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData ("A", 5, 5, "A")]
|
||||
[InlineData (
|
||||
"AB12",
|
||||
5,
|
||||
5,
|
||||
@"
|
||||
A
|
||||
B
|
||||
1
|
||||
2")]
|
||||
[InlineData (
|
||||
"AB\n12",
|
||||
5,
|
||||
5,
|
||||
@"
|
||||
A1
|
||||
B2")]
|
||||
[InlineData ("", 5, 1, "")]
|
||||
[InlineData (
|
||||
"Hello Worlds",
|
||||
1,
|
||||
12,
|
||||
@"
|
||||
H
|
||||
e
|
||||
l
|
||||
l
|
||||
o
|
||||
|
||||
W
|
||||
o
|
||||
r
|
||||
l
|
||||
d
|
||||
s")]
|
||||
[InlineData ("Hello Worlds", 12, 1, @"HelloWorlds")]
|
||||
public void Draw_Vertical_TopBottom_LeftRight (string text, int width, int height, string expectedText)
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Text = text,
|
||||
Direction = TextDirection.TopBottom_LeftRight
|
||||
};
|
||||
|
||||
tf.ConstrainToWidth = width;
|
||||
tf.ConstrainToHeight = height;
|
||||
tf.Draw (new (0, 0, 20, 20), Attribute.Default, Attribute.Default, driver: driver);
|
||||
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
||||
// The expectedY param is to probe that the expectedText param start at that Y coordinate
|
||||
[InlineData ("A", 0, "", 0)]
|
||||
[InlineData ("A", 1, "A", 0)]
|
||||
[InlineData ("A", 2, "A", 0)]
|
||||
[InlineData ("A", 3, "A", 1)]
|
||||
[InlineData ("AB", 1, "A", 0)]
|
||||
[InlineData ("AB", 2, "A\nB", 0)]
|
||||
[InlineData ("ABC", 2, "A\nB", 0)]
|
||||
[InlineData ("ABC", 3, "A\nB\nC", 0)]
|
||||
[InlineData ("ABC", 4, "A\nB\nC", 0)]
|
||||
[InlineData ("ABC", 5, "A\nB\nC", 1)]
|
||||
[InlineData ("ABC", 6, "A\nB\nC", 1)]
|
||||
[InlineData ("ABC", 9, "A\nB\nC", 3)]
|
||||
[InlineData ("ABCD", 2, "B\nC", 0)]
|
||||
[InlineData ("こんにちは", 0, "", 0)]
|
||||
[InlineData ("こんにちは", 1, "に", 0)]
|
||||
[InlineData ("こんにちは", 2, "ん\nに", 0)]
|
||||
[InlineData ("こんにちは", 3, "ん\nに\nち", 0)]
|
||||
[InlineData ("こんにちは", 4, "こ\nん\nに\nち", 0)]
|
||||
[InlineData ("こんにちは", 5, "こ\nん\nに\nち\nは", 0)]
|
||||
[InlineData ("こんにちは", 6, "こ\nん\nに\nち\nは", 0)]
|
||||
[InlineData ("ABCD\nこんにちは", 7, "Aこ\nBん\nCに\nDち\n は", 1)]
|
||||
[InlineData ("こんにちは\nABCD", 7, "こA\nんB\nにC\nちD\nは ", 1)]
|
||||
public void Draw_Vertical_TopBottom_LeftRight_Middle (string text, int height, string expectedText, int expectedY)
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Text = text,
|
||||
Direction = TextDirection.TopBottom_LeftRight,
|
||||
VerticalAlignment = Alignment.Center
|
||||
};
|
||||
|
||||
int width = text.ToRunes ().Max (r => r.GetColumns ());
|
||||
|
||||
if (text.Contains ("\n"))
|
||||
{
|
||||
width++;
|
||||
}
|
||||
|
||||
tf.ConstrainToWidth = width;
|
||||
tf.ConstrainToHeight = height;
|
||||
tf.Draw (new (0, 0, 5, height), Attribute.Default, Attribute.Default, driver: driver);
|
||||
|
||||
Rectangle rect = DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver);
|
||||
Assert.Equal (expectedY, rect.Y);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData ("A", 5, "A")]
|
||||
[InlineData (
|
||||
"AB12",
|
||||
5,
|
||||
@"
|
||||
A
|
||||
B
|
||||
1
|
||||
2")]
|
||||
[InlineData (
|
||||
"AB\n12",
|
||||
5,
|
||||
@"
|
||||
A1
|
||||
B2")]
|
||||
[InlineData ("", 1, "")]
|
||||
[InlineData (
|
||||
"AB1 2",
|
||||
2,
|
||||
@"
|
||||
A12
|
||||
B ")]
|
||||
[InlineData (
|
||||
"こんにちは",
|
||||
1,
|
||||
@"
|
||||
こん")]
|
||||
[InlineData (
|
||||
"こんにちは",
|
||||
2,
|
||||
@"
|
||||
こに
|
||||
んち")]
|
||||
[InlineData (
|
||||
"こんにちは",
|
||||
5,
|
||||
@"
|
||||
こ
|
||||
ん
|
||||
に
|
||||
ち
|
||||
は")]
|
||||
public void Draw_Vertical_TopBottom_LeftRight_Top (string text, int height, string expectedText)
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Text = text,
|
||||
Direction = TextDirection.TopBottom_LeftRight
|
||||
};
|
||||
|
||||
tf.ConstrainToWidth = 5;
|
||||
tf.ConstrainToHeight = height;
|
||||
tf.Draw (new (0, 0, 5, height), Attribute.Default, Attribute.Default, driver: driver);
|
||||
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FillRemaining_True_False ()
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
driver.SetScreenSize (22, 5);
|
||||
|
||||
Attribute [] attrs =
|
||||
{
|
||||
Attribute.Default, new (ColorName16.Green, ColorName16.BrightMagenta),
|
||||
new (ColorName16.Blue, ColorName16.Cyan)
|
||||
};
|
||||
var tf = new TextFormatter { ConstrainToSize = new (14, 3), Text = "Test\nTest long\nTest long long\n", MultiLine = true };
|
||||
|
||||
tf.Draw (
|
||||
new (1, 1, 19, 3),
|
||||
attrs [1],
|
||||
attrs [2],
|
||||
driver: driver);
|
||||
|
||||
Assert.False (tf.FillRemaining);
|
||||
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (
|
||||
@"
|
||||
Test
|
||||
Test long
|
||||
Test long long",
|
||||
output,
|
||||
driver);
|
||||
|
||||
DriverAssert.AssertDriverAttributesAre (
|
||||
@"
|
||||
000000000000000000000
|
||||
011110000000000000000
|
||||
011111111100000000000
|
||||
011111111111111000000
|
||||
000000000000000000000",
|
||||
output,
|
||||
driver,
|
||||
attrs);
|
||||
|
||||
tf.FillRemaining = true;
|
||||
|
||||
tf.Draw (
|
||||
new (1, 1, 19, 3),
|
||||
attrs [1],
|
||||
attrs [2],
|
||||
driver: driver);
|
||||
|
||||
DriverAssert.AssertDriverAttributesAre (
|
||||
@"
|
||||
000000000000000000000
|
||||
011111111111111111110
|
||||
011111111111111111110
|
||||
011111111111111111110
|
||||
000000000000000000000",
|
||||
output,
|
||||
driver,
|
||||
attrs);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData ("Hello World", 15, 1, "Hello World")]
|
||||
[InlineData (
|
||||
"Well Done\nNice Work",
|
||||
15,
|
||||
2,
|
||||
@"
|
||||
Well Done
|
||||
Nice Work")]
|
||||
[InlineData ("你好 世界", 15, 1, "你好 世界")]
|
||||
[InlineData (
|
||||
"做 得好\n幹 得好",
|
||||
15,
|
||||
2,
|
||||
@"
|
||||
做 得好
|
||||
幹 得好")]
|
||||
public void Justify_Horizontal (string text, int width, int height, string expectedText)
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Text = text,
|
||||
Alignment = Alignment.Fill,
|
||||
ConstrainToSize = new Size (width, height),
|
||||
MultiLine = true
|
||||
};
|
||||
|
||||
tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default, driver: driver);
|
||||
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UICatalog_AboutBox_Text ()
|
||||
{
|
||||
IDriver? driver = CreateFakeDriver ();
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Text = UICatalogTop.GetAboutBoxMessage (),
|
||||
Alignment = Alignment.Center,
|
||||
VerticalAlignment = Alignment.Start,
|
||||
WordWrap = false,
|
||||
MultiLine = true,
|
||||
HotKeySpecifier = (Rune)0xFFFF
|
||||
};
|
||||
|
||||
Size tfSize = tf.FormatAndGetSize ();
|
||||
Assert.Equal (new (59, 13), tfSize);
|
||||
|
||||
driver!.SetScreenSize (tfSize.Width, tfSize.Height);
|
||||
|
||||
driver.FillRect (driver.Screen, (Rune)'*');
|
||||
tf.Draw (driver.Screen, Attribute.Default, Attribute.Default, driver: driver);
|
||||
|
||||
var expectedText = """
|
||||
UI Catalog: A comprehensive sample library and test app for
|
||||
***********************************************************
|
||||
_______ _ _ _____ _ *
|
||||
|__ __| (_) | | / ____| (_)*
|
||||
| | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _ *
|
||||
| |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | |*
|
||||
| | __/ | | | | | | | | | | | (_| | || |__| | |_| | |*
|
||||
|_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_|*
|
||||
***********************************************************
|
||||
**********************v2 - Pre-Alpha***********************
|
||||
***********************************************************
|
||||
**********https://github.com/gui-cs/Terminal.Gui***********
|
||||
***********************************************************
|
||||
""";
|
||||
|
||||
DriverAssert.AssertDriverContentsAre (expectedText.ReplaceLineEndings (), output, driver);
|
||||
}
|
||||
|
||||
#region FormatAndGetSizeTests
|
||||
|
||||
// TODO: Add multi-line examples
|
||||
// TODO: Add other TextDirection examples
|
||||
|
||||
[Theory]
|
||||
[InlineData ("界1234", 10, 10, TextDirection.LeftRight_TopBottom, 6, 1, @"界1234")]
|
||||
[InlineData ("01234", 10, 10, TextDirection.LeftRight_TopBottom, 5, 1, @"01234")]
|
||||
[InlineData (
|
||||
"界1234",
|
||||
10,
|
||||
10,
|
||||
TextDirection.TopBottom_LeftRight,
|
||||
2,
|
||||
5,
|
||||
"""
|
||||
界
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
""")]
|
||||
[InlineData (
|
||||
"01234",
|
||||
10,
|
||||
10,
|
||||
TextDirection.TopBottom_LeftRight,
|
||||
1,
|
||||
5,
|
||||
"""
|
||||
0
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
""")]
|
||||
[InlineData (
|
||||
"界1234",
|
||||
3,
|
||||
3,
|
||||
TextDirection.LeftRight_TopBottom,
|
||||
3,
|
||||
2,
|
||||
"""
|
||||
界1
|
||||
234
|
||||
""")]
|
||||
[InlineData (
|
||||
"01234",
|
||||
3,
|
||||
3,
|
||||
TextDirection.LeftRight_TopBottom,
|
||||
3,
|
||||
2,
|
||||
"""
|
||||
012
|
||||
34
|
||||
""")]
|
||||
[InlineData (
|
||||
"界1234",
|
||||
3,
|
||||
3,
|
||||
TextDirection.TopBottom_LeftRight,
|
||||
3,
|
||||
3,
|
||||
"""
|
||||
界3
|
||||
1 4
|
||||
2
|
||||
""")]
|
||||
[InlineData (
|
||||
"01234",
|
||||
3,
|
||||
3,
|
||||
TextDirection.TopBottom_LeftRight,
|
||||
2,
|
||||
3,
|
||||
"""
|
||||
03
|
||||
14
|
||||
2
|
||||
""")]
|
||||
[InlineData ("01234", 2, 1, TextDirection.LeftRight_TopBottom, 2, 1, @"01")]
|
||||
public void FormatAndGetSize_Returns_Correct_Size (
|
||||
string text,
|
||||
int width,
|
||||
int height,
|
||||
TextDirection direction,
|
||||
int expectedWidth,
|
||||
int expectedHeight,
|
||||
string expectedDraw
|
||||
)
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Direction = direction,
|
||||
ConstrainToWidth = width,
|
||||
ConstrainToHeight = height,
|
||||
Text = text
|
||||
};
|
||||
Assert.True (tf.WordWrap);
|
||||
Size size = tf.FormatAndGetSize ();
|
||||
Assert.Equal (new (expectedWidth, expectedHeight), size);
|
||||
|
||||
tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default, driver: driver);
|
||||
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedDraw, output, driver);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData ("界1234", 10, 10, TextDirection.LeftRight_TopBottom, 6, 1, @"界1234")]
|
||||
[InlineData ("01234", 10, 10, TextDirection.LeftRight_TopBottom, 5, 1, @"01234")]
|
||||
[InlineData (
|
||||
"界1234",
|
||||
10,
|
||||
10,
|
||||
TextDirection.TopBottom_LeftRight,
|
||||
2,
|
||||
5,
|
||||
"""
|
||||
界
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
""")]
|
||||
[InlineData (
|
||||
"01234",
|
||||
10,
|
||||
10,
|
||||
TextDirection.TopBottom_LeftRight,
|
||||
1,
|
||||
5,
|
||||
"""
|
||||
0
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
""")]
|
||||
[InlineData ("界1234", 3, 3, TextDirection.LeftRight_TopBottom, 3, 1, @"界1")]
|
||||
[InlineData ("01234", 3, 3, TextDirection.LeftRight_TopBottom, 3, 1, @"012")]
|
||||
[InlineData (
|
||||
"界1234",
|
||||
3,
|
||||
3,
|
||||
TextDirection.TopBottom_LeftRight,
|
||||
2,
|
||||
3,
|
||||
"""
|
||||
界
|
||||
1
|
||||
2
|
||||
""")]
|
||||
[InlineData (
|
||||
"01234",
|
||||
3,
|
||||
3,
|
||||
TextDirection.TopBottom_LeftRight,
|
||||
1,
|
||||
3,
|
||||
"""
|
||||
0
|
||||
1
|
||||
2
|
||||
""")]
|
||||
public void FormatAndGetSize_WordWrap_False_Returns_Correct_Size (
|
||||
string text,
|
||||
int width,
|
||||
int height,
|
||||
TextDirection direction,
|
||||
int expectedWidth,
|
||||
int expectedHeight,
|
||||
string expectedDraw
|
||||
)
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Direction = direction,
|
||||
ConstrainToSize = new (width, height),
|
||||
Text = text,
|
||||
WordWrap = false
|
||||
};
|
||||
Assert.False (tf.WordWrap);
|
||||
Size size = tf.FormatAndGetSize ();
|
||||
Assert.Equal (new (expectedWidth, expectedHeight), size);
|
||||
|
||||
tf.Draw (new (0, 0, width, height), Attribute.Default, Attribute.Default, driver: driver);
|
||||
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedDraw, output, driver);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,18 +2,10 @@
|
||||
using UnitTests;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
// Alias Console to MockConsole so we don't accidentally use Console
|
||||
|
||||
namespace UnitTests_Parallelizable.TextTests;
|
||||
|
||||
public class TextFormatterTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
public class TextFormatterTests (ITestOutputHelper output) : FakeDriverBase
|
||||
{
|
||||
private readonly ITestOutputHelper _output;
|
||||
|
||||
public TextFormatterTests (ITestOutputHelper output)
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
[Theory]
|
||||
[InlineData ("")]
|
||||
[InlineData (null)]
|
||||
@@ -720,7 +712,7 @@ public class TextFormatterTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
foreach (Alignment alignment in Enum.GetValues (typeof (Alignment)))
|
||||
{
|
||||
TextFormatter tf = new ()
|
||||
{ Text = text, ConstrainToSize = new (1, maxWidth), WordWrap = wrap, VerticalAlignment = alignment, Direction = TextDirection.TopBottom_LeftRight };
|
||||
{ Text = text, ConstrainToSize = new (1, maxWidth), WordWrap = wrap, VerticalAlignment = alignment, Direction = TextDirection.TopBottom_LeftRight };
|
||||
|
||||
List<string> list = TextFormatter.Format (
|
||||
text,
|
||||
@@ -2913,7 +2905,7 @@ public class TextFormatterTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
[InlineData ("This has lf in the end\n", "This has lf in the end")]
|
||||
public void StripCRLF_RemovesCrLf (string input, string expected)
|
||||
{
|
||||
string actual = TextFormatter.StripCRLF(input, keepNewLine: false);
|
||||
string actual = TextFormatter.StripCRLF (input, keepNewLine: false);
|
||||
Assert.Equal (expected, actual);
|
||||
}
|
||||
|
||||
@@ -2937,7 +2929,7 @@ public class TextFormatterTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
[InlineData ("This has lf in the end\n", "This has lf in the end\n")]
|
||||
public void StripCRLF_KeepNewLine_RemovesCarriageReturnFromCrLf (string input, string expected)
|
||||
{
|
||||
string actual = TextFormatter.StripCRLF(input, keepNewLine: true);
|
||||
string actual = TextFormatter.StripCRLF (input, keepNewLine: true);
|
||||
Assert.Equal (expected, actual);
|
||||
}
|
||||
|
||||
@@ -2961,7 +2953,7 @@ public class TextFormatterTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
[InlineData ("This has lf in the end\n", "This has lf in the end ")]
|
||||
public void ReplaceCRLFWithSpace_ReplacesCrLfWithSpace (string input, string expected)
|
||||
{
|
||||
string actual = TextFormatter.ReplaceCRLFWithSpace(input);
|
||||
string actual = TextFormatter.ReplaceCRLFWithSpace (input);
|
||||
Assert.Equal (expected, actual);
|
||||
}
|
||||
|
||||
@@ -2986,7 +2978,7 @@ public class TextFormatterTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
public void Draw_Horizontal_Centered (string text, int width, string expectedText)
|
||||
{
|
||||
var driver = CreateFakeDriver (width > 0 ? width : 1, 1);
|
||||
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Text = text,
|
||||
@@ -2997,7 +2989,7 @@ public class TextFormatterTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
|
||||
tf.Draw (new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default, default, driver);
|
||||
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedText, _output, driver);
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -3015,7 +3007,7 @@ public class TextFormatterTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
public void Draw_Horizontal_Justified (string text, int width, string expectedText)
|
||||
{
|
||||
var driver = CreateFakeDriver (width > 0 ? width : 1, 1);
|
||||
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Text = text,
|
||||
@@ -3026,7 +3018,7 @@ public class TextFormatterTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
|
||||
tf.Draw (new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default, default, driver);
|
||||
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedText, _output, driver);
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -3041,7 +3033,7 @@ public class TextFormatterTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
public void Draw_Horizontal_Left (string text, int width, string expectedText)
|
||||
{
|
||||
var driver = CreateFakeDriver (width > 0 ? width : 1, 1);
|
||||
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Text = text,
|
||||
@@ -3052,7 +3044,7 @@ public class TextFormatterTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
|
||||
tf.Draw (new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default, default, driver);
|
||||
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedText, _output, driver);
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -3067,7 +3059,7 @@ public class TextFormatterTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
public void Draw_Horizontal_Right (string text, int width, string expectedText)
|
||||
{
|
||||
var driver = CreateFakeDriver (width > 0 ? width : 1, 1);
|
||||
|
||||
|
||||
TextFormatter tf = new ()
|
||||
{
|
||||
Text = text,
|
||||
@@ -3078,6 +3070,162 @@ public class TextFormatterTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
|
||||
tf.Draw (new Rectangle (0, 0, width, 1), Attribute.Default, Attribute.Default, default, driver);
|
||||
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedText, _output, driver);
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expectedText, output, driver);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (14, 1, TextDirection.LeftRight_TopBottom, "Les Misęrables")]
|
||||
[InlineData (1, 14, TextDirection.TopBottom_LeftRight, "L\ne\ns\n \nM\ni\ns\nę\nr\na\nb\nl\ne\ns")]
|
||||
[InlineData (
|
||||
4,
|
||||
4,
|
||||
TextDirection.TopBottom_LeftRight,
|
||||
@"
|
||||
LMre
|
||||
eias
|
||||
ssb
|
||||
ęl "
|
||||
)]
|
||||
public void Draw_With_Combining_Runes (int width, int height, TextDirection textDirection, string expected)
|
||||
{
|
||||
var driver = CreateFakeDriver ();
|
||||
var text = "Les Mise\u0328\u0301rables";
|
||||
|
||||
var tf = new TextFormatter ();
|
||||
tf.Direction = textDirection;
|
||||
tf.Text = text;
|
||||
|
||||
Assert.True (tf.WordWrap);
|
||||
|
||||
tf.ConstrainToSize = new (width, height);
|
||||
|
||||
tf.Draw (
|
||||
new (0, 0, width, height),
|
||||
new (ColorName16.White, ColorName16.Black),
|
||||
new (ColorName16.Blue, ColorName16.Black),
|
||||
default (Rectangle),
|
||||
driver
|
||||
);
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expected, output, driver);
|
||||
|
||||
driver.End ();
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")]
|
||||
[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
|
||||
[InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
|
||||
[InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
|
||||
public void TabWith_PreserveTrailingSpaces_True (
|
||||
int width,
|
||||
int height,
|
||||
TextDirection textDirection,
|
||||
int tabWidth,
|
||||
string expected
|
||||
)
|
||||
{
|
||||
var driver = CreateFakeDriver ();
|
||||
var text = "This is a \tTab";
|
||||
var tf = new TextFormatter ();
|
||||
|
||||
tf.Direction = textDirection;
|
||||
tf.TabWidth = tabWidth;
|
||||
tf.PreserveTrailingSpaces = true;
|
||||
tf.Text = text;
|
||||
tf.ConstrainToWidth = 20;
|
||||
tf.ConstrainToHeight = 20;
|
||||
|
||||
Assert.True (tf.WordWrap);
|
||||
|
||||
tf.Draw (
|
||||
new (0, 0, width, height),
|
||||
new (ColorName16.White, ColorName16.Black),
|
||||
new (ColorName16.Blue, ColorName16.Black),
|
||||
default (Rectangle),
|
||||
driver
|
||||
);
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expected, output, driver);
|
||||
|
||||
driver.End ();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")]
|
||||
[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
|
||||
[InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
|
||||
[InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
|
||||
public void TabWith_WordWrap_True (
|
||||
int width,
|
||||
int height,
|
||||
TextDirection textDirection,
|
||||
int tabWidth,
|
||||
string expected
|
||||
)
|
||||
{
|
||||
var driver = CreateFakeDriver ();
|
||||
|
||||
var text = "This is a \tTab";
|
||||
var tf = new TextFormatter ();
|
||||
|
||||
tf.Direction = textDirection;
|
||||
tf.TabWidth = tabWidth;
|
||||
tf.WordWrap = true;
|
||||
tf.Text = text;
|
||||
tf.ConstrainToWidth = 20;
|
||||
tf.ConstrainToHeight = 20;
|
||||
|
||||
Assert.False (tf.PreserveTrailingSpaces);
|
||||
|
||||
tf.Draw (
|
||||
new (0, 0, width, height),
|
||||
new (ColorName16.White, ColorName16.Black),
|
||||
new (ColorName16.Blue, ColorName16.Black),
|
||||
default (Rectangle),
|
||||
driver
|
||||
);
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expected, output, driver);
|
||||
|
||||
driver.End ();
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData (17, 1, TextDirection.LeftRight_TopBottom, 4, "This is a Tab")]
|
||||
[InlineData (1, 17, TextDirection.TopBottom_LeftRight, 4, "T\nh\ni\ns\n \ni\ns\n \na\n \n \n \n \n \nT\na\nb")]
|
||||
[InlineData (13, 1, TextDirection.LeftRight_TopBottom, 0, "This is a Tab")]
|
||||
[InlineData (1, 13, TextDirection.TopBottom_LeftRight, 0, "T\nh\ni\ns\n \ni\ns\n \na\n \nT\na\nb")]
|
||||
public void TabWith_PreserveTrailingSpaces_False (
|
||||
int width,
|
||||
int height,
|
||||
TextDirection textDirection,
|
||||
int tabWidth,
|
||||
string expected
|
||||
)
|
||||
{
|
||||
var driver = CreateFakeDriver ();
|
||||
|
||||
var text = "This is a \tTab";
|
||||
var tf = new TextFormatter ();
|
||||
tf.Direction = textDirection;
|
||||
tf.TabWidth = tabWidth;
|
||||
tf.Text = text;
|
||||
tf.ConstrainToWidth = 20;
|
||||
tf.ConstrainToHeight = 20;
|
||||
|
||||
Assert.True (tf.WordWrap);
|
||||
Assert.False (tf.PreserveTrailingSpaces);
|
||||
|
||||
tf.Draw (
|
||||
new (0, 0, width, height),
|
||||
new (ColorName16.White, ColorName16.Black),
|
||||
new (ColorName16.Blue, ColorName16.Black),
|
||||
default (Rectangle),
|
||||
driver
|
||||
);
|
||||
DriverAssert.AssertDriverContentsWithFrameAre (expected, output, driver);
|
||||
|
||||
driver.End ();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\UnitTests\TestsAllViews.cs" Link="TestsAllViews.cs" />
|
||||
<Compile Include="..\UnitTests\DriverAssert.cs" Link="DriverAssert.cs" />
|
||||
<Compile Include="..\UnitTests\OutputAssert.cs" Link="OutputAssert.cs" />
|
||||
</ItemGroup>
|
||||
@@ -53,9 +52,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Examples\UICatalog\UICatalog.csproj" />
|
||||
<ProjectReference Include="..\..\Terminal.Gui\Terminal.Gui.csproj">
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\TerminalGuiFluentTesting\TerminalGuiFluentTesting.csproj" />
|
||||
<ProjectReference Include="..\UnitTests\UnitTests.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -68,23 +69,4 @@
|
||||
<Using Include="Terminal.Gui" />
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="FineCodeCoverage">
|
||||
<Enabled>
|
||||
False
|
||||
</Enabled>
|
||||
<Exclude>
|
||||
[UICatalog]*
|
||||
</Exclude>
|
||||
<Include></Include>
|
||||
<ExcludeByFile>
|
||||
<!--**/Migrations/*
|
||||
**/Hacks/*.cs-->
|
||||
</ExcludeByFile>
|
||||
<ExcludeByAttribute>
|
||||
<!--MyCustomExcludeFromCodeCoverage-->
|
||||
</ExcludeByAttribute>
|
||||
<IncludeTestAssembly>
|
||||
False
|
||||
</IncludeTestAssembly>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -13,7 +13,6 @@ 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)
|
||||
{
|
||||
@@ -56,7 +55,6 @@ 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)
|
||||
{
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#nullable enable
|
||||
using UnitTests.Parallelizable;
|
||||
using UnitTests;
|
||||
using Xunit;
|
||||
|
||||
namespace UnitTests_Parallelizable.ViewTests;
|
||||
|
||||
[Trait ("Category", "View.Scheme")]
|
||||
public class SchemeTests : ParallelizableBase
|
||||
public class SchemeTests : FakeDriverBase
|
||||
{
|
||||
|
||||
[Fact]
|
||||
|
||||
57
Tests/UnitTestsParallelizable/Views/AllViewsDrawTests.cs
Normal file
57
Tests/UnitTestsParallelizable/Views/AllViewsDrawTests.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
#nullable enable
|
||||
using UnitTests;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace UnitTests_Parallelizable.ViewsTests;
|
||||
|
||||
public class AllViewsDrawTests (ITestOutputHelper output) : TestsAllViews
|
||||
{
|
||||
[Theory]
|
||||
[MemberData (nameof (AllViewTypes))]
|
||||
public void AllViews_Draw_Does_Not_Layout (Type viewType)
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
View? view = CreateInstanceIfNotGeneric (viewType);
|
||||
|
||||
if (view is null)
|
||||
{
|
||||
output.WriteLine ($"Ignoring {viewType} - It's a Generic");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
output.WriteLine ($"Testing {viewType}");
|
||||
|
||||
if (view is IDesignable designable)
|
||||
{
|
||||
designable.EnableForDesign ();
|
||||
}
|
||||
|
||||
var drawCompleteCount = 0;
|
||||
view.DrawComplete += (s, e) => drawCompleteCount++;
|
||||
|
||||
var layoutStartedCount = 0;
|
||||
view.SubViewLayout += (s, e) => layoutStartedCount++;
|
||||
|
||||
var layoutCompleteCount = 0;
|
||||
view.SubViewsLaidOut += (s, e) => layoutCompleteCount++;
|
||||
|
||||
view.SetNeedsLayout ();
|
||||
view.Layout ();
|
||||
|
||||
Assert.Equal (0, drawCompleteCount);
|
||||
Assert.Equal (1, layoutStartedCount);
|
||||
Assert.Equal (1, layoutCompleteCount);
|
||||
|
||||
if (view.Visible)
|
||||
{
|
||||
view.SetNeedsDraw ();
|
||||
view.Draw ();
|
||||
|
||||
Assert.Equal (1, drawCompleteCount);
|
||||
Assert.Equal (1, layoutStartedCount);
|
||||
Assert.Equal (1, layoutCompleteCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +1,108 @@
|
||||
using System.Reflection;
|
||||
using Microsoft.VisualStudio.TestPlatform.Utilities;
|
||||
#nullable enable
|
||||
using System.Reflection;
|
||||
using UnitTests;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace UnitTests_Parallelizable.ViewsTests;
|
||||
|
||||
[Collection ("Global Test Setup")]
|
||||
public class AllViewsTests (ITestOutputHelper output) : TestsAllViews
|
||||
{
|
||||
[Theory]
|
||||
[MemberData (nameof (AllViewTypes))]
|
||||
public void AllViews_Layout_Does_Not_Draw (Type viewType)
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
View? view = CreateInstanceIfNotGeneric (viewType);
|
||||
|
||||
if (view is null)
|
||||
{
|
||||
output.WriteLine ($"Ignoring {viewType} - It's a Generic");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (view is IDesignable designable)
|
||||
{
|
||||
designable.EnableForDesign ();
|
||||
}
|
||||
|
||||
var drawContentCount = 0;
|
||||
view.DrawingContent += (s, e) => drawContentCount++;
|
||||
|
||||
var layoutStartedCount = 0;
|
||||
view.SubViewLayout += (s, e) => layoutStartedCount++;
|
||||
|
||||
var layoutCompleteCount = 0;
|
||||
view.SubViewsLaidOut += (s, e) => layoutCompleteCount++;
|
||||
|
||||
view.SetNeedsLayout ();
|
||||
view.SetNeedsDraw ();
|
||||
view.Layout ();
|
||||
|
||||
Assert.Equal (0, drawContentCount);
|
||||
Assert.Equal (1, layoutStartedCount);
|
||||
Assert.Equal (1, layoutCompleteCount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData (nameof (AllViewTypes))]
|
||||
public void AllViews_Center_Properly (Type viewType)
|
||||
{
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
View? view = CreateInstanceIfNotGeneric (viewType);
|
||||
|
||||
if (view is null)
|
||||
{
|
||||
output.WriteLine ($"Ignoring {viewType} - It's a Generic");
|
||||
return;
|
||||
}
|
||||
|
||||
if (view is IDesignable designable)
|
||||
{
|
||||
designable.EnableForDesign ();
|
||||
}
|
||||
|
||||
view.X = Pos.Center ();
|
||||
view.Y = Pos.Center ();
|
||||
|
||||
// Ensure the view has positive dimensions
|
||||
view.Width = 10;
|
||||
view.Height = 10;
|
||||
|
||||
var frame = new View { X = 0, Y = 0, Width = 50, Height = 50 };
|
||||
frame.Add (view);
|
||||
frame.LayoutSubViews ();
|
||||
frame.Dispose ();
|
||||
|
||||
// What's the natural width/height?
|
||||
int expectedX = (frame.Frame.Width - view.Frame.Width) / 2;
|
||||
int expectedY = (frame.Frame.Height - view.Frame.Height) / 2;
|
||||
|
||||
Assert.True (
|
||||
view.Frame.Left == expectedX,
|
||||
$"{view} did not center horizontally. Expected: {expectedX}. Actual: {view.Frame.Left}"
|
||||
);
|
||||
|
||||
Assert.True (
|
||||
view.Frame.Top == expectedY,
|
||||
$"{view} did not center vertically. Expected: {expectedY}. Actual: {view.Frame.Top}"
|
||||
);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData (nameof (AllViewTypes))]
|
||||
public void AllViews_Tests_All_Constructors (Type viewType)
|
||||
{
|
||||
Assert.True (TestAllConstructorsOfType (viewType));
|
||||
|
||||
return;
|
||||
|
||||
bool TestAllConstructorsOfType (Type type)
|
||||
{
|
||||
foreach (ConstructorInfo ctor in type.GetConstructors ())
|
||||
{
|
||||
View view = CreateViewFromType (type, ctor);
|
||||
View? view = CreateViewFromType (type, ctor);
|
||||
|
||||
if (view != null)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using UnitTests;
|
||||
|
||||
namespace UnitTests_Parallelizable.ViewsTests;
|
||||
|
||||
/// <summary>
|
||||
/// Pure unit tests for <see cref="Button"/> that don't require Application static dependencies.
|
||||
/// These tests can run in parallel without interference.
|
||||
/// </summary>
|
||||
public class ButtonTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
public class ButtonTests : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void Text_Mirrors_Title ()
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using UnitTests;
|
||||
|
||||
namespace UnitTests_Parallelizable.ViewsTests;
|
||||
|
||||
/// <summary>
|
||||
/// Pure unit tests for <see cref="ColorPicker"/> that don't require Application.Driver or View context.
|
||||
/// These tests can run in parallel without interference.
|
||||
/// </summary>
|
||||
public class ColorPickerTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
public class ColorPickerTests : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void ColorPicker_ChangedEvent_Fires ()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Globalization;
|
||||
using UnitTests;
|
||||
|
||||
namespace UnitTests_Parallelizable.ViewsTests;
|
||||
|
||||
@@ -6,7 +7,7 @@ namespace UnitTests_Parallelizable.ViewsTests;
|
||||
/// Pure unit tests for <see cref="DatePicker"/> that don't require Application.Driver or View context.
|
||||
/// These tests can run in parallel without interference.
|
||||
/// </summary>
|
||||
public class DatePickerTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
public class DatePickerTests : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void DatePicker_ChangingCultureChangesFormat ()
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using UnitTests;
|
||||
|
||||
namespace UnitTests_Parallelizable.ViewsTests;
|
||||
|
||||
/// <summary>
|
||||
/// Pure unit tests for <see cref="Label"/> that don't require Application.Driver or Application context.
|
||||
/// These tests can run in parallel without interference.
|
||||
/// </summary>
|
||||
public class LabelTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
public class LabelTests : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void Text_Mirrors_Title ()
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System.Text;
|
||||
using UnitTests;
|
||||
|
||||
namespace UnitTests_Parallelizable.ViewsTests;
|
||||
|
||||
public class SliderOptionTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
public class SliderOptionTests : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void OnChanged_Should_Raise_ChangedEvent ()
|
||||
@@ -94,7 +95,7 @@ public class SliderOptionTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
}
|
||||
}
|
||||
|
||||
public class SliderEventArgsTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
public class SliderEventArgsTests : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_Sets_Cancel_Default_To_False ()
|
||||
@@ -138,7 +139,7 @@ public class SliderEventArgsTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
}
|
||||
}
|
||||
|
||||
public class SliderTests : UnitTests.Parallelizable.ParallelizableBase
|
||||
public class SliderTests : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_Default ()
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using System.Text;
|
||||
using UnitTests;
|
||||
using UnitTests.Parallelizable;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace UnitTests_Parallelizable.ViewsTests;
|
||||
|
||||
public class TextFieldTests (ITestOutputHelper output) : ParallelizableBase
|
||||
public class TextFieldTests (ITestOutputHelper output) : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void Cancel_TextChanging_ThenBackspace ()
|
||||
@@ -561,7 +560,7 @@ public class TextFieldTests (ITestOutputHelper output) : ParallelizableBase
|
||||
[Fact]
|
||||
public void Accented_Letter_With_Three_Combining_Unicode_Chars ()
|
||||
{
|
||||
IConsoleDriver driver = CreateFakeDriver ();
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
var tf = new TextField { Width = 3, Text = "ắ" };
|
||||
tf.Driver = driver;
|
||||
@@ -612,7 +611,7 @@ public class TextFieldTests (ITestOutputHelper output) : ParallelizableBase
|
||||
[Fact]
|
||||
public void Adjust_First ()
|
||||
{
|
||||
IConsoleDriver driver = CreateFakeDriver ();
|
||||
IDriver driver = CreateFakeDriver ();
|
||||
|
||||
var tf = new TextField { Width = Dim.Fill (), Text = "This is a test." };
|
||||
tf.Driver = driver;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using UnitTests;
|
||||
|
||||
namespace UnitTests_Parallelizable.ViewsTests;
|
||||
|
||||
public class TextValidateField_NET_Provider_Tests : UnitTests.Parallelizable.ParallelizableBase
|
||||
public class TextValidateField_NET_Provider_Tests : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void Backspace_Key_Deletes_Previous_Character ()
|
||||
@@ -425,7 +426,7 @@ public class TextValidateField_NET_Provider_Tests : UnitTests.Parallelizable.Par
|
||||
}
|
||||
}
|
||||
|
||||
public class TextValidateField_Regex_Provider_Tests : UnitTests.Parallelizable.ParallelizableBase
|
||||
public class TextValidateField_Regex_Provider_Tests : FakeDriverBase
|
||||
{
|
||||
[Fact]
|
||||
public void End_Key_End_Of_Input ()
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<RunSettings>
|
||||
<DataCollectionRunSettings>
|
||||
<DataCollectors>
|
||||
<DataCollector friendlyName="XPlat code coverage">
|
||||
<Configuration>
|
||||
<Format>opencover</Format>
|
||||
<Exclude>[UnitTests]*,[UICatalog]*,[coverlet.*.tests?]*,[*]Coverlet.Core*</Exclude> <!-- [Assembly-Filter]Type-Filter -->
|
||||
<Include></Include> <!-- [Assembly-Filter]Type-Filter -->
|
||||
<ExcludeByAttribute></ExcludeByAttribute>
|
||||
<ExcludeByFile></ExcludeByFile> <!-- Globbing filter -->
|
||||
<IncludeDirectory></IncludeDirectory>
|
||||
<SingleHit>false</SingleHit>
|
||||
<UseSourceLink>true</UseSourceLink>
|
||||
<IncludeTestAssembly>true</IncludeTestAssembly>
|
||||
<SkipAutoProps>true</SkipAutoProps>
|
||||
<DeterministicReport>false</DeterministicReport>
|
||||
<ResultsDirectory>TestResults/</ResultsDirectory>
|
||||
</Configuration>
|
||||
</DataCollector>
|
||||
</DataCollectors>
|
||||
</DataCollectionRunSettings>
|
||||
</RunSettings>
|
||||
34
Tests/UnitTestsParallelizable/runsettings.coverage.xml
Normal file
34
Tests/UnitTestsParallelizable/runsettings.coverage.xml
Normal file
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RunSettings>
|
||||
<!-- Existing Coverlet/Code Coverage Config -->
|
||||
<DataCollectionRunSettings>
|
||||
<DataCollectors>
|
||||
<DataCollector friendlyName="XPlat Code Coverage">
|
||||
<Configuration>
|
||||
<Format>cobertura</Format>
|
||||
<ExcludeByFile>**/obj/**/*.*</ExcludeByFile>
|
||||
<Exclude>[*]*.g.cs</Exclude>
|
||||
<Exclude>[*]*AssemblyInfo.cs</Exclude>
|
||||
<!-- ONLY include Terminal.Gui assembly -->
|
||||
<Include>[Terminal.Gui]*</Include>
|
||||
</Configuration>
|
||||
</DataCollector>
|
||||
</DataCollectors>
|
||||
</DataCollectionRunSettings>
|
||||
|
||||
<!-- New: xUnit Config for Stop-on-Fail -->
|
||||
<xUnit>
|
||||
<ParallelizeAssembly>true</ParallelizeAssembly>
|
||||
<ParallelizeTestCollections>true</ParallelizeTestCollections>
|
||||
<!-- Enable collection parallelism -->
|
||||
<MaxParallelThreads>unlimited</MaxParallelThreads>
|
||||
<!-- Or 'unlimited' / '2x' for CPU multiplier -->
|
||||
<StopOnFail>true</StopOnFail>
|
||||
<!-- Still stop on first failure -->
|
||||
</xUnit>
|
||||
|
||||
<!-- Optional: Global Run Config (e.g., results dir) -->
|
||||
<RunConfiguration>
|
||||
<ResultsDirectory>../TestResults</ResultsDirectory>
|
||||
</RunConfiguration>
|
||||
</RunSettings>
|
||||
15
Tests/UnitTestsParallelizable/runsettings.xml
Normal file
15
Tests/UnitTestsParallelizable/runsettings.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RunSettings>
|
||||
|
||||
<!-- New: xUnit Config for Stop-on-Fail -->
|
||||
<xUnit>
|
||||
<ParallelizeAssembly>true</ParallelizeAssembly>
|
||||
<ParallelizeTestCollections>true</ParallelizeTestCollections>
|
||||
<!-- Enable collection parallelism -->
|
||||
<MaxParallelThreads>unlimited</MaxParallelThreads>
|
||||
<!-- Or 'unlimited' / '2x' for CPU multiplier -->
|
||||
<StopOnFail>true</StopOnFail>
|
||||
<!-- Still stop on first failure -->
|
||||
</xUnit>
|
||||
|
||||
</RunSettings>
|
||||
Reference in New Issue
Block a user