Merge branch 'gui-cs:v2_develop' into v2_develop

This commit is contained in:
Tig
2026-02-03 10:09:57 -07:00
committed by GitHub
8 changed files with 607 additions and 146 deletions

View File

@@ -12,18 +12,53 @@ public static class InputInjectionExtensions
/// Injects a key event (convenience method).
/// </summary>
/// <param name="key">The key to inject.</param>
public void InjectKey (Key key) { app.GetInputInjector ().InjectKey (key); }
public void InjectKey (Key key) => app.GetInputInjector ().InjectKey (key);
/// <summary>
/// Injects a mouse event (convenience method).
/// </summary>
/// <param name="mouseEvent">The mouse event to inject.</param>
public void InjectMouse (Mouse mouseEvent) { app.GetInputInjector ().InjectMouse (mouseEvent); }
public void InjectMouse (Mouse mouseEvent) => app.GetInputInjector ().InjectMouse (mouseEvent);
/// <summary>
/// Injects a sequence of events (convenience method).
/// </summary>
/// <param name="events">The events to inject.</param>
public void InjectSequence (params InputInjectionEvent [] events) { app.GetInputInjector ().InjectSequence (events); }
public void InjectSequence (params InputInjectionEvent [] events) => app.GetInputInjector ().InjectSequence (events);
}
/// <summary>
/// Gets the injection events for a left button click at the specified point.
/// </summary>
/// <param name="p">The screen position for the click.</param>
/// <returns>An array of injection events representing a left button click.</returns>
public static InputInjectionEvent [] LeftButtonClick (Point p) =>
[
new MouseInjectionEvent (new Mouse { Flags = MouseFlags.LeftButtonPressed, ScreenPosition = p }) { Delay = TimeSpan.FromMilliseconds (10) },
new MouseInjectionEvent (new Mouse { Flags = MouseFlags.LeftButtonReleased, ScreenPosition = p }) { Delay = TimeSpan.FromMilliseconds (10) }
];
/// <summary>
/// Gets the injection events for a right button click at the specified point.
/// </summary>
/// <param name="p">The screen position for the click.</param>
/// <returns>An array of injection events representing a right button click.</returns>
public static InputInjectionEvent [] RightButtonClick (Point p) =>
[
new MouseInjectionEvent (new Mouse { Flags = MouseFlags.RightButtonPressed, ScreenPosition = p }) { Delay = TimeSpan.FromMilliseconds (10) },
new MouseInjectionEvent (new Mouse { Flags = MouseFlags.RightButtonReleased, ScreenPosition = p }) { Delay = TimeSpan.FromMilliseconds (10) }
];
/// <summary>
/// Gets the injection events for a left button double-click at the specified point.
/// </summary>
/// <param name="p">The screen position for the double-click.</param>
/// <returns>An array of injection events representing a left button double-click.</returns>
public static InputInjectionEvent [] LeftButtonDoubleClick (Point p) =>
[
new MouseInjectionEvent (new Mouse { Flags = MouseFlags.LeftButtonPressed, ScreenPosition = p }) { Delay = TimeSpan.FromMilliseconds (0) },
new MouseInjectionEvent (new Mouse { Flags = MouseFlags.LeftButtonReleased, ScreenPosition = p }) { Delay = TimeSpan.FromMilliseconds (10) },
new MouseInjectionEvent (new Mouse { Flags = MouseFlags.LeftButtonPressed, ScreenPosition = p }) { Delay = TimeSpan.FromMilliseconds (10) },
new MouseInjectionEvent (new Mouse { Flags = MouseFlags.LeftButtonReleased, ScreenPosition = p }) { Delay = TimeSpan.FromMilliseconds (10) }
];
}

View File

@@ -69,6 +69,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{C7A51224-5
docfx\docs\events.md = docfx\docs\events.md
docfx\docs\getting-started.md = docfx\docs\getting-started.md
docfx\docs\index.md = docfx\docs\index.md
docfx\docs\input-injection.md = docfx\docs\input-injection.md
docfx\docs\keyboard.md = docfx\docs\keyboard.md
docfx\docs\layout.md = docfx\docs\layout.md
docfx\docs\lexicon.md = docfx\docs\lexicon.md

View File

@@ -25,7 +25,7 @@ public class InputInjectionExampleTests
using IApplication app = Application.Create (time);
app.Init (DriverRegistry.Names.ANSI);
int keyPressed = 0;
var keyPressed = 0;
Key? receivedKey = null;
app.Keyboard.KeyDown += (s, e) =>
@@ -63,18 +63,11 @@ public class InputInjectionExampleTests
app.Mouse.MouseEvent += (s, e) => { receivedEvents.Add (e.Flags); };
// Act - Inject press and release with controlled timing
Mouse press = new ()
{
ScreenPosition = new (5, 5),
Flags = MouseFlags.LeftButtonPressed,
Timestamp = time.Now
};
Mouse press = new () { ScreenPosition = new (5, 5), Flags = MouseFlags.LeftButtonPressed, Timestamp = time.Now };
Mouse release = new ()
{
ScreenPosition = new (5, 5),
Flags = MouseFlags.LeftButtonReleased,
Timestamp = time.Now.AddMilliseconds (50) // Controlled 50ms delay
ScreenPosition = new (5, 5), Flags = MouseFlags.LeftButtonReleased, Timestamp = time.Now.AddMilliseconds (50) // Controlled 50ms delay
};
app.InjectMouse (press);
@@ -144,7 +137,7 @@ public class InputInjectionExampleTests
using IApplication app = Application.Create (time);
app.Init (DriverRegistry.Names.ANSI);
string typedText = "";
var typedText = "";
app.Keyboard.KeyDown += (s, e) =>
{
@@ -223,8 +216,7 @@ public class InputInjectionExampleTests
app.Mouse.MouseEvent += (s, e) =>
{
if (e.Flags.HasFlag (MouseFlags.LeftButtonClicked) ||
e.Flags.HasFlag (MouseFlags.LeftButtonDoubleClicked))
if (e.Flags.HasFlag (MouseFlags.LeftButtonClicked) || e.Flags.HasFlag (MouseFlags.LeftButtonDoubleClicked))
{
clicks.Add (e.Flags);
}
@@ -280,4 +272,127 @@ public class InputInjectionExampleTests
}
#endregion
#region Example 8: Mouse Click Helper Methods
/// <summary>
/// Example 8: Using helper methods for common mouse click patterns.
/// These helpers encapsulate common sequences for cleaner test code.
/// </summary>
[Fact]
public void Example8_MouseClickHelpers ()
{
// Arrange
VirtualTimeProvider time = new ();
using IApplication app = Application.Create (time);
app.Init (DriverRegistry.Names.ANSI);
using Runnable runnable = new ();
app.Begin (runnable);
Button button = new () { Text = "Click Me", X = 5, Y = 2 };
runnable?.Add (button);
var acceptingCalled = false;
button.Accepting += (s, e) => acceptingCalled = true;
// Act - Use helper for simple left-click at button position
app.InjectSequence (InputInjectionExtensions.LeftButtonClick (new Point (5, 2)));
// Assert
Assert.True (acceptingCalled);
// Reset for next test
acceptingCalled = false;
// Right-click helper
List<MouseFlags> receivedFlags = [];
app.Mouse.MouseEvent += (s, e) => receivedFlags.Add (e.Flags);
app.InjectSequence (InputInjectionExtensions.RightButtonClick (new Point (10, 5)));
Assert.Contains (receivedFlags, f => f.HasFlag (MouseFlags.RightButtonClicked));
// Double-click helper
CheckBox checkBox = new () { Text = "_Check", X = 0, Y = 0 };
runnable?.Add (checkBox);
CheckState initialState = checkBox.Value;
app.InjectSequence (InputInjectionExtensions.LeftButtonDoubleClick (new Point (0, 0)));
// After double-click, state should have toggled twice (back to initial)
Assert.Equal (initialState, checkBox.Value);
runnable?.Dispose ();
}
#endregion
#region Tests for https: //github.com/gui-cs/Terminal.Gui/issues/4675
[Fact]
public void Issue_4675_MakeInjectingDoubleClickEasier_WorksAsExpected ()
{
// This test reproduces the exact scenario from the issue
using IApplication app = Application.Create ();
app.Init (DriverRegistry.Names.ANSI);
using Runnable runnable = new ();
app.Begin (runnable);
CheckBox checkBox = new () { Text = "_Checkbox" };
runnable?.Add (checkBox);
CheckState initialState = checkBox.Value;
// This is the simplified syntax requested in the issue
app.InjectSequence (InputInjectionExtensions.LeftButtonDoubleClick (new Point (0, 0)));
// After double-click, checkbox should have toggled twice (back to initial)
Assert.Equal (initialState, checkBox.Value);
runnable?.Dispose ();
}
[Fact]
public void Issue_4675_LeftButtonClick_WorksAsExpected ()
{
using IApplication app = Application.Create ();
app.Init (DriverRegistry.Names.ANSI);
using Runnable runnable = new ();
app.Begin (runnable);
Button button = new () { Text = "Click Me" };
runnable?.Add (button);
var acceptingCalled = false;
button.Accepting += (s, e) => acceptingCalled = true;
// Test the LeftButtonClick helper
app.InjectSequence (InputInjectionExtensions.LeftButtonClick (new Point (0, 0)));
Assert.True (acceptingCalled);
runnable?.Dispose ();
}
[Fact]
public void Issue_4675_RightButtonClick_WorksAsExpected ()
{
using IApplication app = Application.Create ();
app.Init (DriverRegistry.Names.ANSI);
List<MouseFlags> receivedFlags = [];
app.Mouse.MouseEvent += (s, e) => receivedFlags.Add (e.Flags);
// Test the RightButtonClick helper
app.InjectSequence (InputInjectionExtensions.RightButtonClick (new Point (5, 5)));
Assert.Contains (receivedFlags, f => f.HasFlag (MouseFlags.RightButtonPressed));
Assert.Contains (receivedFlags, f => f.HasFlag (MouseFlags.RightButtonReleased));
Assert.Contains (receivedFlags, f => f.HasFlag (MouseFlags.RightButtonClicked));
}
#endregion
}

View File

@@ -0,0 +1,275 @@
namespace InputTests;
/// <summary>
/// Tests for InputInjectionExtensions helper methods.
/// These tests verify the helper methods that create common mouse click sequences.
/// </summary>
[Trait ("Category", "Input")]
[Trait ("Category", "InputInjection")]
public class InputInjectionExtensionsTests
{
#region LeftButtonClick Tests
[Fact]
public void LeftButtonClick_ReturnsCorrectSequence ()
{
// Arrange
Point clickPosition = new (10, 5);
// Act
InputInjectionEvent [] events = InputInjectionExtensions.LeftButtonClick (clickPosition);
// Assert
Assert.NotNull (events);
Assert.Equal (2, events.Length);
// First event: LeftButtonPressed
Assert.IsType<MouseInjectionEvent> (events [0]);
MouseInjectionEvent pressEvent = (MouseInjectionEvent)events [0];
Assert.Equal (MouseFlags.LeftButtonPressed, pressEvent.Mouse.Flags);
Assert.Equal (clickPosition, pressEvent.Mouse.ScreenPosition);
Assert.Equal (TimeSpan.FromMilliseconds (10), pressEvent.Delay);
// Second event: LeftButtonReleased
Assert.IsType<MouseInjectionEvent> (events [1]);
MouseInjectionEvent releaseEvent = (MouseInjectionEvent)events [1];
Assert.Equal (MouseFlags.LeftButtonReleased, releaseEvent.Mouse.Flags);
Assert.Equal (clickPosition, releaseEvent.Mouse.ScreenPosition);
Assert.Equal (TimeSpan.FromMilliseconds (10), releaseEvent.Delay);
}
[Fact]
public void LeftButtonClick_WithApplication_ProducesClickEvent ()
{
// Arrange
VirtualTimeProvider time = new ();
using IApplication app = Application.Create (time);
app.Init (DriverRegistry.Names.ANSI);
List<MouseFlags> receivedFlags = [];
app.Mouse.MouseEvent += (s, e) => receivedFlags.Add (e.Flags);
Point clickPosition = new (5, 5);
// Act
app.InjectSequence (InputInjectionExtensions.LeftButtonClick (clickPosition));
// Assert - Should receive Press, Release, and synthesized Click
Assert.Contains (receivedFlags, f => f.HasFlag (MouseFlags.LeftButtonPressed));
Assert.Contains (receivedFlags, f => f.HasFlag (MouseFlags.LeftButtonReleased));
Assert.Contains (receivedFlags, f => f.HasFlag (MouseFlags.LeftButtonClicked));
Assert.Equal (3, receivedFlags.Count);
}
#endregion
#region RightButtonClick Tests
[Fact]
public void RightButtonClick_ReturnsCorrectSequence ()
{
// Arrange
Point clickPosition = new (15, 8);
// Act
InputInjectionEvent [] events = InputInjectionExtensions.RightButtonClick (clickPosition);
// Assert
Assert.NotNull (events);
Assert.Equal (2, events.Length);
// First event: RightButtonPressed
Assert.IsType<MouseInjectionEvent> (events [0]);
MouseInjectionEvent pressEvent = (MouseInjectionEvent)events [0];
Assert.Equal (MouseFlags.RightButtonPressed, pressEvent.Mouse.Flags);
Assert.Equal (clickPosition, pressEvent.Mouse.ScreenPosition);
Assert.Equal (TimeSpan.FromMilliseconds (10), pressEvent.Delay);
// Second event: RightButtonReleased
Assert.IsType<MouseInjectionEvent> (events [1]);
MouseInjectionEvent releaseEvent = (MouseInjectionEvent)events [1];
Assert.Equal (MouseFlags.RightButtonReleased, releaseEvent.Mouse.Flags);
Assert.Equal (clickPosition, releaseEvent.Mouse.ScreenPosition);
Assert.Equal (TimeSpan.FromMilliseconds (10), releaseEvent.Delay);
}
[Fact]
public void RightButtonClick_WithApplication_ProducesClickEvent ()
{
// Arrange
VirtualTimeProvider time = new ();
using IApplication app = Application.Create (time);
app.Init (DriverRegistry.Names.ANSI);
List<MouseFlags> receivedFlags = [];
app.Mouse.MouseEvent += (s, e) => receivedFlags.Add (e.Flags);
Point clickPosition = new (3, 2);
// Act
app.InjectSequence (InputInjectionExtensions.RightButtonClick (clickPosition));
// Assert - Should receive Press, Release, and synthesized Click
Assert.Contains (receivedFlags, f => f.HasFlag (MouseFlags.RightButtonPressed));
Assert.Contains (receivedFlags, f => f.HasFlag (MouseFlags.RightButtonReleased));
Assert.Contains (receivedFlags, f => f.HasFlag (MouseFlags.RightButtonClicked));
Assert.Equal (3, receivedFlags.Count);
}
#endregion
#region LeftButtonDoubleClick Tests
[Fact]
public void LeftButtonDoubleClick_ReturnsCorrectSequence ()
{
// Arrange
Point clickPosition = new (20, 10);
// Act
InputInjectionEvent [] events = InputInjectionExtensions.LeftButtonDoubleClick (clickPosition);
// Assert
Assert.NotNull (events);
Assert.Equal (4, events.Length);
// First click: Press
Assert.IsType<MouseInjectionEvent> (events [0]);
MouseInjectionEvent firstPress = (MouseInjectionEvent)events [0];
Assert.Equal (MouseFlags.LeftButtonPressed, firstPress.Mouse.Flags);
Assert.Equal (clickPosition, firstPress.Mouse.ScreenPosition);
Assert.Equal (TimeSpan.FromMilliseconds (0), firstPress.Delay);
// First click: Release
Assert.IsType<MouseInjectionEvent> (events [1]);
MouseInjectionEvent firstRelease = (MouseInjectionEvent)events [1];
Assert.Equal (MouseFlags.LeftButtonReleased, firstRelease.Mouse.Flags);
Assert.Equal (clickPosition, firstRelease.Mouse.ScreenPosition);
Assert.Equal (TimeSpan.FromMilliseconds (10), firstRelease.Delay);
// Second click: Press
Assert.IsType<MouseInjectionEvent> (events [2]);
MouseInjectionEvent secondPress = (MouseInjectionEvent)events [2];
Assert.Equal (MouseFlags.LeftButtonPressed, secondPress.Mouse.Flags);
Assert.Equal (clickPosition, secondPress.Mouse.ScreenPosition);
Assert.Equal (TimeSpan.FromMilliseconds (10), secondPress.Delay);
// Second click: Release
Assert.IsType<MouseInjectionEvent> (events [3]);
MouseInjectionEvent secondRelease = (MouseInjectionEvent)events [3];
Assert.Equal (MouseFlags.LeftButtonReleased, secondRelease.Mouse.Flags);
Assert.Equal (clickPosition, secondRelease.Mouse.ScreenPosition);
Assert.Equal (TimeSpan.FromMilliseconds (10), secondRelease.Delay);
}
[Fact]
public void LeftButtonDoubleClick_WithApplication_ProducesDoubleClickEvent ()
{
// Arrange
VirtualTimeProvider time = new ();
using IApplication app = Application.Create (time);
app.Init (DriverRegistry.Names.ANSI);
List<MouseFlags> receivedFlags = [];
app.Mouse.MouseEvent += (s, e) => receivedFlags.Add (e.Flags);
Point clickPosition = new (7, 4);
// Act
app.InjectSequence (InputInjectionExtensions.LeftButtonDoubleClick (clickPosition));
// Assert - Should receive two clicks plus double-click event
int pressCount = receivedFlags.Count (f => f.HasFlag (MouseFlags.LeftButtonPressed));
int releaseCount = receivedFlags.Count (f => f.HasFlag (MouseFlags.LeftButtonReleased));
Assert.Equal (2, pressCount);
Assert.Equal (2, releaseCount);
Assert.Contains (receivedFlags, f => f.HasFlag (MouseFlags.LeftButtonDoubleClicked));
}
#endregion
#region Integration Tests
[Fact]
public void LeftButtonClick_OnButton_TriggersAccepting ()
{
// Arrange
VirtualTimeProvider time = new ();
using IApplication app = Application.Create (time);
app.Init (DriverRegistry.Names.ANSI);
using Runnable runnable = new ();
app.Begin (runnable);
Button button = new () { Text = "Click Me" };
(runnable as View)?.Add (button);
var acceptingCalled = false;
button.Accepting += (s, e) => acceptingCalled = true;
// Act
app.InjectSequence (InputInjectionExtensions.LeftButtonClick (new Point (0, 0)));
// Assert
Assert.True (acceptingCalled);
(runnable as View)?.Dispose ();
}
[Fact]
public void LeftButtonDoubleClick_OnCheckBox_TogglesCheckState ()
{
// Arrange
VirtualTimeProvider time = new ();
using IApplication app = Application.Create (time);
app.Init (DriverRegistry.Names.ANSI);
using Runnable runnable = new ();
app.Begin (runnable);
CheckBox checkBox = new () { Text = "_Checkbox" };
(runnable as View)?.Add (checkBox);
CheckState initialState = checkBox.Value;
// Act - Double-click should toggle twice, returning to initial state
app.InjectSequence (InputInjectionExtensions.LeftButtonDoubleClick (new Point (0, 0)));
// Assert - After double-click, state should be back to initial
Assert.Equal (initialState, checkBox.Value);
(runnable as View)?.Dispose ();
}
[Fact]
public void RightButtonClick_OnView_RaisesMouseEvent ()
{
// Arrange
VirtualTimeProvider time = new ();
using IApplication app = Application.Create (time);
app.Init (DriverRegistry.Names.ANSI);
using Runnable runnable = new ();
app.Begin (runnable);
View testView = new () { Width = 10, Height = 5 };
(runnable as View)?.Add (testView);
var rightClickReceived = false;
testView.MouseEvent += (s, e) =>
{
if (e.Flags.HasFlag (MouseFlags.RightButtonClicked))
{
rightClickReceived = true;
}
};
// Act
app.InjectSequence (InputInjectionExtensions.RightButtonClick (new Point (5, 2)));
// Assert
Assert.True (rightClickReceived);
(runnable as View)?.Dispose ();
}
#endregion
}

View File

@@ -24,21 +24,8 @@ public class MouseInjectionDocTests
var acceptingCalled = false;
button.Accepting += (s, e) => acceptingCalled = true;
// Single-call injection - press
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonPressed,
ScreenPosition = new (0, 0)
});
// Single-call injection - release
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonReleased,
ScreenPosition = new (0, 0)
});
// Use helper method for simplified click injection
app.InjectSequence (InputInjectionExtensions.LeftButtonClick (new (0, 0)));
Assert.True (acceptingCalled);
(runnable as View)?.Dispose ();
@@ -59,36 +46,20 @@ public class MouseInjectionDocTests
app.Mouse.MouseEvent += (s, e) => receivedFlags.Add (e.Flags);
// First click at T+0
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonPressed
});
app.InjectMouse (new () { Flags = MouseFlags.LeftButtonPressed });
time.Advance (TimeSpan.FromMilliseconds (50));
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonReleased
});
app.InjectMouse (new () { Flags = MouseFlags.LeftButtonReleased });
// Second click at T+350 (within 500ms double-click threshold)
time.Advance (TimeSpan.FromMilliseconds (300));
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonPressed
});
app.InjectMouse (new () { Flags = MouseFlags.LeftButtonPressed });
time.Advance (TimeSpan.FromMilliseconds (50));
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonReleased
});
app.InjectMouse (new () { Flags = MouseFlags.LeftButtonReleased });
// Verify double-click detected
Assert.Contains (receivedFlags, f => f.HasFlag (MouseFlags.LeftButtonDoubleClicked));
@@ -107,36 +78,20 @@ public class MouseInjectionDocTests
app.Mouse.MouseEvent += (s, e) => receivedFlags.Add (e.Flags);
// First click at T+0
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonPressed
});
app.InjectMouse (new () { Flags = MouseFlags.LeftButtonPressed });
time.Advance (TimeSpan.FromMilliseconds (50));
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonReleased
});
app.InjectMouse (new () { Flags = MouseFlags.LeftButtonReleased });
// Second click at T+600 (outside 500ms threshold)
time.Advance (TimeSpan.FromMilliseconds (600));
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonPressed
});
app.InjectMouse (new () { Flags = MouseFlags.LeftButtonPressed });
time.Advance (TimeSpan.FromMilliseconds (50));
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonReleased
});
app.InjectMouse (new () { Flags = MouseFlags.LeftButtonReleased });
// Verify double-click NOT detected
Assert.DoesNotContain (receivedFlags, f => f.HasFlag (MouseFlags.LeftButtonDoubleClicked));
@@ -160,53 +115,29 @@ public class MouseInjectionDocTests
app.Mouse.MouseEvent += (s, e) => receivedFlags.Add (e.Flags);
// First click
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonPressed
});
app.InjectMouse (new () { Flags = MouseFlags.LeftButtonPressed });
time.Advance (TimeSpan.FromMilliseconds (50));
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonReleased
});
app.InjectMouse (new () { Flags = MouseFlags.LeftButtonReleased });
// Second click (within 500ms)
time.Advance (TimeSpan.FromMilliseconds (200));
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonPressed
});
app.InjectMouse (new () { Flags = MouseFlags.LeftButtonPressed });
time.Advance (TimeSpan.FromMilliseconds (50));
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonReleased
});
app.InjectMouse (new () { Flags = MouseFlags.LeftButtonReleased });
// Third click (within 500ms of second)
time.Advance (TimeSpan.FromMilliseconds (200));
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonPressed
});
app.InjectMouse (new () { Flags = MouseFlags.LeftButtonPressed });
time.Advance (TimeSpan.FromMilliseconds (50));
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonReleased
});
app.InjectMouse (new () { Flags = MouseFlags.LeftButtonReleased });
// Verify triple-click detected
Assert.Contains (receivedFlags, f => f.HasFlag (MouseFlags.LeftButtonTripleClicked));
@@ -234,17 +165,9 @@ public class MouseInjectionDocTests
};
// Right click
app.InjectMouse (
new ()
{
Flags = MouseFlags.RightButtonPressed
});
app.InjectMouse (new () { Flags = MouseFlags.RightButtonPressed });
app.InjectMouse (
new ()
{
Flags = MouseFlags.RightButtonReleased, Position = new (10, 10)
});
app.InjectMouse (new () { Flags = MouseFlags.RightButtonReleased, Position = new (10, 10) });
Assert.True (rightClickReceived);
}
@@ -267,17 +190,9 @@ public class MouseInjectionDocTests
};
// Middle click
app.InjectMouse (
new ()
{
Flags = MouseFlags.MiddleButtonPressed
});
app.InjectMouse (new () { Flags = MouseFlags.MiddleButtonPressed });
app.InjectMouse (
new ()
{
Flags = MouseFlags.MiddleButtonReleased
});
app.InjectMouse (new () { Flags = MouseFlags.MiddleButtonReleased });
Assert.True (middleClickReceived);
}
@@ -291,10 +206,7 @@ public class MouseInjectionDocTests
using IApplication app = Application.Create (time);
app.Init (DriverRegistry.Names.ANSI);
View view = new ()
{
Width = 10, Height = 10,
};
View view = new () { Width = 10, Height = 10 };
Point? lastPosition = null;
view.MouseEvent += (s, e) =>
@@ -303,6 +215,7 @@ public class MouseInjectionDocTests
{
lastPosition = e.Position;
}
if (e.Flags.HasFlag (MouseFlags.PositionReport) && lastPosition.HasValue && e.Position.HasValue)
{
// Handle drag
@@ -317,29 +230,14 @@ public class MouseInjectionDocTests
app.Begin (runnable);
// Press at (5, 5)
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonPressed,
ScreenPosition = new (0, 0)
});
app.InjectMouse (new () { Flags = MouseFlags.LeftButtonPressed, ScreenPosition = new (0, 0) });
// Drag to (10, 10)
app.InjectMouse (
new ()
{
Flags = MouseFlags.PositionReport | MouseFlags.LeftButtonPressed,
ScreenPosition = new (5, 5)
});
app.InjectMouse (new () { Flags = MouseFlags.PositionReport | MouseFlags.LeftButtonPressed, ScreenPosition = new (5, 5) });
app.LayoutAndDraw ();
// Release
app.InjectMouse (
new ()
{
Flags = MouseFlags.LeftButtonReleased,
ScreenPosition = new (5, 5)
});
app.InjectMouse (new () { Flags = MouseFlags.LeftButtonReleased, ScreenPosition = new (5, 5) });
Assert.Equal (5, view.Frame.X);
Assert.Equal (5, view.Frame.Y);

View File

@@ -0,0 +1,11 @@
namespace TestingTests;
/// <summary>
/// Test that validates the exact scenario from the GitHub issue.
/// </summary>
[Trait ("Category", "Input")]
[Trait ("Category", "InputInjection")]
public class IssueScenarioTest
{
}

View File

@@ -30,6 +30,8 @@ app.InjectKey(Key.Esc);
### Basic Mouse Input
**Recommended: Use helper methods for common click patterns**
```csharp
using IApplication app = Application.Create();
app.Init(DriverRegistry.Names.ANSI);
@@ -37,7 +39,20 @@ app.Init(DriverRegistry.Names.ANSI);
// Subscribe to mouse events
app.Mouse.MouseEvent += (s, e) => Console.WriteLine($"Mouse: {e.Flags} at {e.ScreenPosition}");
// Inject mouse click
// Inject a left click using the helper method
app.InjectSequence(InputInjectionExtensions.LeftButtonClick(new Point(10, 5)));
// Inject a right click
app.InjectSequence(InputInjectionExtensions.RightButtonClick(new Point(10, 5)));
// Inject a double-click
app.InjectSequence(InputInjectionExtensions.LeftButtonDoubleClick(new Point(10, 5)));
```
**Alternative: Manual event creation (for advanced scenarios)**
```csharp
// Inject mouse click manually (two separate events)
app.InjectMouse(new() {
ScreenPosition = new(10, 5),
Flags = MouseFlags.LeftButtonPressed
@@ -94,6 +109,72 @@ app.InjectMouse(new() {
// Double-click was detected!
```
### Mouse Click Helper Methods
For simplified mouse click injection, use the `InputInjectionExtensions` helper methods. These encapsulate common click patterns and reduce boilerplate code.
#### Available Helpers
**`LeftButtonClick(Point p)`** - Single left mouse click
```csharp
app.InjectSequence(InputInjectionExtensions.LeftButtonClick(new Point(10, 5)));
```
**`RightButtonClick(Point p)`** - Single right mouse click
```csharp
app.InjectSequence(InputInjectionExtensions.RightButtonClick(new Point(10, 5)));
```
**`LeftButtonDoubleClick(Point p)`** - Double-click
```csharp
app.InjectSequence(InputInjectionExtensions.LeftButtonDoubleClick(new Point(10, 5)));
```
#### Benefits
- **Less code** - One line instead of multiple `InjectMouse` calls
- **Built-in timing** - Appropriate delays for reliable click detection
- **Fewer errors** - No need to manually match Press/Release pairs
- **Clearer intent** - Code explicitly shows what user action is being simulated
#### Before and After Comparison
**Before (manual):**
```csharp
// Double-click requires 4 separate events
app.InjectMouse(new() { ScreenPosition = new(10, 5), Flags = MouseFlags.LeftButtonPressed });
app.InjectMouse(new() { ScreenPosition = new(10, 5), Flags = MouseFlags.LeftButtonReleased });
app.InjectMouse(new() { ScreenPosition = new(10, 5), Flags = MouseFlags.LeftButtonPressed });
app.InjectMouse(new() { ScreenPosition = new(10, 5), Flags = MouseFlags.LeftButtonReleased });
```
**After (helper):**
```csharp
// Same behavior, one line
app.InjectSequence(InputInjectionExtensions.LeftButtonDoubleClick(new Point(10, 5)));
```
#### Example: Testing Button Click
```csharp
using IApplication app = Application.Create();
app.Init(DriverRegistry.Names.ANSI);
using Runnable runnable = new();
app.Begin(runnable);
Button button = new() { Text = "Click Me" };
runnable.Add(button);
var acceptingCalled = false;
button.Accepting += (s, e) => acceptingCalled = true;
// Use helper to inject click
app.InjectSequence(InputInjectionExtensions.LeftButtonClick(new Point(0, 0)));
Assert.True(acceptingCalled);
```
## When to Use Each Mode
### Direct Mode (Default)

View File

@@ -627,14 +627,32 @@ This ensures consistent mouse behavior across platforms while maintaining platfo
Terminal.Gui provides sophisticated input injection for testing without hardware:
### Quick Test Example
### Quick Test Example (Using Helper Methods)
**Recommended approach** - Use helper methods for cleaner test code:
```csharp
using IApplication app = Application.Create();
app.Init(DriverRegistry.Names.ANSI);
// Inject a left click - simple and clear
app.InjectSequence(InputInjectionExtensions.LeftButtonClick(new Point(10, 5)));
// Inject a right click
app.InjectSequence(InputInjectionExtensions.RightButtonClick(new Point(10, 5)));
// Inject a double-click
app.InjectSequence(InputInjectionExtensions.LeftButtonDoubleClick(new Point(10, 5)));
```
**Alternative approach** - Manual event creation for advanced scenarios:
```csharp
VirtualTimeProvider time = new();
using IApplication app = Application.Create(time);
app.Init(DriverRegistry.Names.ANSI);
// Inject click
// Inject click manually
app.InjectMouse(new() {
ScreenPosition = new(10, 5),
Flags = MouseFlags.LeftButtonPressed
@@ -647,6 +665,18 @@ app.InjectMouse(new() {
### Testing Double-Click with Virtual Time
**Using helper method** (recommended):
```csharp
using IApplication app = Application.Create();
app.Init(DriverRegistry.Names.ANSI);
// One line for a complete double-click
app.InjectSequence(InputInjectionExtensions.LeftButtonDoubleClick(new Point(10, 5)));
```
**Manual approach** (for custom timing control):
```csharp
VirtualTimeProvider time = new();
time.SetTime(new DateTime(2025, 1, 1, 12, 0, 0));
@@ -682,9 +712,24 @@ app.InjectMouse(new() {
// Double-click detected!
```
### Mouse Click Helper Methods
Terminal.Gui provides three helper methods in `InputInjectionExtensions` to simplify common mouse click patterns:
- **`LeftButtonClick(Point p)`** - Single left click (Press + Release)
- **`RightButtonClick(Point p)`** - Single right click (Press + Release)
- **`LeftButtonDoubleClick(Point p)`** - Double left click (two complete click sequences)
**Benefits:**
- Reduces boilerplate from 2-4 lines to 1 line
- Built-in appropriate delays for reliable click detection
- Clearer test intent
- Fewer errors from mismatched Press/Release pairs
### Key Testing Features
- **Virtual Time Control** - Deterministic multi-click timing
- **Helper Methods** - `InputInjectionExtensions.LeftButtonClick()`, etc. for simplified injection
- **Single-Call Injection** - `app.InjectMouse(mouse)` handles everything
- **No Real Delays** - Tests run instantly with virtual time
- **Two Modes** - Direct (fast) and Pipeline (full ANSI encoding)