mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2026-02-10 04:03:41 +01:00
merged
This commit is contained in:
@@ -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) }
|
||||
];
|
||||
}
|
||||
|
||||
@@ -463,6 +463,28 @@ public abstract record Dim : IEqualityOperators<Dim, Dim, bool>
|
||||
/// </returns>
|
||||
internal virtual bool CanContributeToAutoSizing => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minimum contribution this Dim makes to auto-sizing calculations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method is used by <see cref="DimAuto"/> to determine the minimum size contribution
|
||||
/// of a Dim during auto-sizing calculations. The default implementation returns the
|
||||
/// result of <see cref="Calculate"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Types that have special minimum contribution logic (like <see cref="DimFill"/> with
|
||||
/// <see cref="DimFill.MinimumContentDim"/>) should override this method.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="location">The starting point from where the size calculation begins.</param>
|
||||
/// <param name="superviewContentSize">The size of the SuperView's content.</param>
|
||||
/// <param name="us">The View that holds this Dim object.</param>
|
||||
/// <param name="dimension">Width or Height</param>
|
||||
/// <returns>The minimum size contribution for auto-sizing calculations.</returns>
|
||||
internal virtual int GetMinimumContribution (int location, int superviewContentSize, View us, Dimension dimension) =>
|
||||
Calculate (location, superviewContentSize, us, dimension);
|
||||
|
||||
#endregion virtual methods
|
||||
|
||||
#region operators
|
||||
|
||||
@@ -387,18 +387,20 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt
|
||||
.ToList ();
|
||||
}
|
||||
|
||||
// Process DimFill views with MinimumContentDim or To
|
||||
// Process DimFill views that can contribute
|
||||
for (var i = 0; i < contributingDimFillSubViews.Count; i++)
|
||||
{
|
||||
View dimFillSubView = contributingDimFillSubViews [i];
|
||||
DimFill? dimFill = dimension == Dimension.Width ? dimFillSubView.Width as DimFill : dimFillSubView.Height as DimFill;
|
||||
Dim dimFill = dimension == Dimension.Width ? dimFillSubView.Width : dimFillSubView.Height;
|
||||
|
||||
if (dimFill?.MinimumContentDim is { })
|
||||
// Get the minimum contribution from the Dim itself
|
||||
int minContribution = dimFill.GetMinimumContribution (0, maxCalculatedSize, dimFillSubView, dimension);
|
||||
|
||||
if (minContribution > 0)
|
||||
{
|
||||
// This DimFill has a minimum - it contributes to auto-sizing
|
||||
int minSize = dimFill.MinimumContentDim.Calculate (0, maxCalculatedSize, dimFillSubView, dimension);
|
||||
// Add position offset to get total size needed
|
||||
int positionOffset = dimension == Dimension.Width ? dimFillSubView.Frame.X : dimFillSubView.Frame.Y;
|
||||
int totalSize = positionOffset + minSize;
|
||||
int totalSize = positionOffset + minContribution;
|
||||
|
||||
if (totalSize > maxCalculatedSize)
|
||||
{
|
||||
@@ -406,13 +408,13 @@ public record DimAuto (Dim? MaximumContentDim, Dim? MinimumContentDim, DimAutoSt
|
||||
}
|
||||
}
|
||||
|
||||
if (dimFill?.To is { })
|
||||
// Handle special case for DimFill with To (still needs type-specific logic)
|
||||
if (dimFill is DimFill dimFillTyped && dimFillTyped.To is { })
|
||||
{
|
||||
// This DimFill has a To view - it contributes to auto-sizing
|
||||
// The SuperView needs to be large enough to contain both the dimFillSubView and the To view
|
||||
int dimFillPos = dimension == Dimension.Width ? dimFillSubView.Frame.X : dimFillSubView.Frame.Y;
|
||||
int toViewPos = dimension == Dimension.Width ? dimFill.To.Frame.X : dimFill.To.Frame.Y;
|
||||
int toViewSize = dimension == Dimension.Width ? dimFill.To.Frame.Width : dimFill.To.Frame.Height;
|
||||
int toViewPos = dimension == Dimension.Width ? dimFillTyped.To.Frame.X : dimFillTyped.To.Frame.Y;
|
||||
int toViewSize = dimension == Dimension.Width ? dimFillTyped.To.Frame.Width : dimFillTyped.To.Frame.Height;
|
||||
int totalSize = int.Max (dimFillPos, toViewPos + toViewSize);
|
||||
|
||||
if (totalSize > maxCalculatedSize)
|
||||
|
||||
@@ -95,6 +95,28 @@ public record DimCombine (AddOrSubtract Add, Dim Left, Dim Right) : Dim
|
||||
/// <inheritdoc/>
|
||||
internal override bool CanContributeToAutoSizing => Left.CanContributeToAutoSizing || Right.CanContributeToAutoSizing;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override int GetMinimumContribution (int location, int superviewContentSize, View us, Dimension dimension)
|
||||
{
|
||||
int newDimension;
|
||||
|
||||
if (Add == AddOrSubtract.Add)
|
||||
{
|
||||
newDimension = Left.GetMinimumContribution (location, superviewContentSize, us, dimension)
|
||||
+ Right.GetMinimumContribution (location, superviewContentSize, us, dimension);
|
||||
}
|
||||
else
|
||||
{
|
||||
newDimension = Math.Max (
|
||||
0,
|
||||
Left.GetMinimumContribution (location, superviewContentSize, us, dimension)
|
||||
- Right.GetMinimumContribution (location, superviewContentSize, us, dimension)
|
||||
);
|
||||
}
|
||||
|
||||
return newDimension;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool HasInner<TDim> (out TDim dim) => Left.Has (out dim) || Right.Has (out dim);
|
||||
}
|
||||
|
||||
@@ -94,6 +94,21 @@ public record DimFill (Dim Margin, Dim? MinimumContentDim = null, View? To = nul
|
||||
/// <inheritdoc/>
|
||||
internal override bool CanContributeToAutoSizing => MinimumContentDim is { } || To is { };
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override int GetMinimumContribution (int location, int superviewContentSize, View us, Dimension dimension)
|
||||
{
|
||||
// DimFill's minimum contribution depends on MinimumContentDim and To
|
||||
if (MinimumContentDim is { })
|
||||
{
|
||||
// Return the minimum content dimension
|
||||
return MinimumContentDim.Calculate (0, superviewContentSize, us, dimension);
|
||||
}
|
||||
|
||||
// If only To is set (without MinimumContentDim), we need special handling in DimAuto
|
||||
// because the contribution depends on the To view's position
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override bool HasInner<TDim> (out TDim dim)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public class InputProcessorImplTests (ITestOutputHelper output)
|
||||
}
|
||||
|
||||
// CoPilot: claude-3-7-sonnet-20250219
|
||||
[Fact (Skip = "Flaky test - needs investigation")]
|
||||
[Fact (Skip = "Flaky test causing random GitHub workflow failures - see issue")]
|
||||
public async Task ProcessQueue_DoesNotReleaseEscape_BeforeTimeout ()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -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);
|
||||
|
||||
11
Tests/UnitTestsParallelizable/Testing/IssueScenarioTest.cs
Normal file
11
Tests/UnitTestsParallelizable/Testing/IssueScenarioTest.cs
Normal 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
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
#nullable disable
|
||||
|
||||
// Claude - Opus 4.5
|
||||
|
||||
namespace ViewBaseTests.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for the GetMinimumContribution method on Dim types.
|
||||
/// This method is used by DimAuto to get minimum size contribution during auto-sizing.
|
||||
/// </summary>
|
||||
public class GetMinimumContributionTests
|
||||
{
|
||||
[Fact]
|
||||
public void DimAbsolute_GetMinimumContribution_ReturnsAbsoluteValue ()
|
||||
{
|
||||
Dim dim = Dim.Absolute (42);
|
||||
int contribution = dim.GetMinimumContribution (0, 100, null, Dimension.None);
|
||||
Assert.Equal (42, contribution);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DimAuto_GetMinimumContribution_ReturnsCalculatedValue ()
|
||||
{
|
||||
var view = new View { Width = Dim.Auto (), Height = Dim.Auto () };
|
||||
view.BeginInit ();
|
||||
view.EndInit ();
|
||||
|
||||
// DimAuto calculates based on content
|
||||
int contribution = view.Width.GetMinimumContribution (0, 100, view, Dimension.Width);
|
||||
Assert.True (contribution >= 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DimFunc_GetMinimumContribution_ReturnsCalculatedValue ()
|
||||
{
|
||||
Dim dim = Dim.Func (_ => 25);
|
||||
int contribution = dim.GetMinimumContribution (0, 100, null, Dimension.None);
|
||||
Assert.Equal (25, contribution);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DimView_GetMinimumContribution_ReturnsTargetViewSize ()
|
||||
{
|
||||
var targetView = new View { Width = 30, Height = 20 };
|
||||
Dim dim = Dim.Width (targetView);
|
||||
|
||||
int contribution = dim.GetMinimumContribution (0, 100, null, Dimension.Width);
|
||||
Assert.Equal (30, contribution);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DimPercent_GetMinimumContribution_ReturnsCalculatedPercentage ()
|
||||
{
|
||||
Dim dim = Dim.Percent (50);
|
||||
int contribution = dim.GetMinimumContribution (0, 100, null, Dimension.None);
|
||||
Assert.Equal (50, contribution);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DimFill_WithoutMinimumContentDim_ReturnsZero ()
|
||||
{
|
||||
Dim dim = Dim.Fill ();
|
||||
int contribution = dim.GetMinimumContribution (0, 100, null, Dimension.None);
|
||||
Assert.Equal (0, contribution);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DimFill_WithMargin_WithoutMinimumContentDim_ReturnsZero ()
|
||||
{
|
||||
Dim dim = Dim.Fill (5);
|
||||
int contribution = dim.GetMinimumContribution (0, 100, null, Dimension.None);
|
||||
Assert.Equal (0, contribution);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DimFill_WithMinimumContentDim_ReturnsMinimumSize ()
|
||||
{
|
||||
Dim dim = Dim.Fill (0, 35);
|
||||
int contribution = dim.GetMinimumContribution (0, 100, null, Dimension.None);
|
||||
Assert.Equal (35, contribution);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DimFill_WithMarginAndMinimumContentDim_ReturnsMinimumSize ()
|
||||
{
|
||||
Dim dim = Dim.Fill (5, 40);
|
||||
int contribution = dim.GetMinimumContribution (0, 100, null, Dimension.None);
|
||||
Assert.Equal (40, contribution);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DimFill_WithMinimumContentDim_UsesCalculatedValue ()
|
||||
{
|
||||
// MinimumContentDim can also be dynamic (e.g., Percent)
|
||||
Dim dim = Dim.Fill (0, Dim.Percent (25));
|
||||
int contribution = dim.GetMinimumContribution (0, 100, null, Dimension.None);
|
||||
Assert.Equal (25, contribution);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DimFill_WithTo_ReturnsZero ()
|
||||
{
|
||||
// When only To is set (without MinimumContentDim), GetMinimumContribution returns 0
|
||||
// because the actual contribution depends on the To view's position, which DimAuto handles specially
|
||||
var toView = new View { X = 50, Y = 30 };
|
||||
Dim dim = Dim.Fill (toView);
|
||||
|
||||
int contribution = dim.GetMinimumContribution (0, 100, null, Dimension.Width);
|
||||
Assert.Equal (0, contribution);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DimFill_WithMarginAndTo_ReturnsZero ()
|
||||
{
|
||||
var toView = new View { X = 50, Y = 30 };
|
||||
Dim dim = Dim.Fill (5, toView);
|
||||
|
||||
int contribution = dim.GetMinimumContribution (0, 100, null, Dimension.Width);
|
||||
Assert.Equal (0, contribution);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DimCombine_GetMinimumContribution_CombinesValues ()
|
||||
{
|
||||
// Addition
|
||||
Dim dim1 = Dim.Absolute (10) + Dim.Absolute (5);
|
||||
int contribution1 = dim1.GetMinimumContribution (0, 100, null, Dimension.None);
|
||||
Assert.Equal (15, contribution1);
|
||||
|
||||
// Subtraction
|
||||
Dim dim2 = Dim.Absolute (20) - Dim.Absolute (8);
|
||||
int contribution2 = dim2.GetMinimumContribution (0, 100, null, Dimension.None);
|
||||
Assert.Equal (12, contribution2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DimCombine_WithDimFill_HandlesMinimumContentDim ()
|
||||
{
|
||||
// DimFill with MinimumContentDim + Absolute
|
||||
Dim dim = Dim.Fill (0, 30) + Dim.Absolute (10);
|
||||
int contribution = dim.GetMinimumContribution (0, 100, null, Dimension.None);
|
||||
Assert.Equal (40, contribution);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DimCombine_WithDimFill_WithoutMinimum_UsesZero ()
|
||||
{
|
||||
// DimFill without MinimumContentDim + Absolute = 0 + 10 = 10
|
||||
Dim dim = Dim.Fill () + Dim.Absolute (10);
|
||||
int contribution = dim.GetMinimumContribution (0, 100, null, Dimension.None);
|
||||
Assert.Equal (10, contribution);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user