Files
Terminal.Gui/Tests/UnitTests/View/Mouse/MouseTests.cs
Copilot e7a4df492d Fixes #4050. Rename Command.Select and Selecting to Activate/Activating (#4470)
* Initial plan

* Rename Command.Select to Command.Activate and Selecting to Activating

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Add Activating event propagation to SuperView

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Update all comments and docs referencing Select to Activate

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Fix event log messages in examples to use Activating/Activate

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Revert automatic Activating event propagation that broke tests

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* Update docfx documentation to use Activate/Activating terminology

Co-authored-by: tig <585482+tig@users.noreply.github.com>

* renames

* Revert "Add Activating event propagation to SuperView"

This reverts commit 6d82bee9ad.

* added command diagrams

* mermaid

* updated level 3

* again

* Select->Activate in MouseTests.cs

* Update Terminal.Gui/Views/Selectors/FlagSelector.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Refactor: Rename Selecting to Activating in View APIs

Renamed the `Selecting` event and `OnSelecting` method to
`Activating` and `OnActivating` to better reflect their purpose.
Updated all related comments, test method names, variables,
and assertions in `View` and `ViewCommandTests` to align with
the new terminology.

Improved code clarity by using `_` for unused parameters in
lambda expressions. Renamed properties like `HandleSelecting`
to `HandleActivating` and adjusted naming conventions for
consistency (e.g., `OnactivatingCount` to `OnActivatingCount`).

These changes enhance readability, maintainability, and
terminology consistency across the codebase.

* Update Terminal.Gui/Views/Selectors/OptionSelector.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Typos

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tig <585482+tig@users.noreply.github.com>
Co-authored-by: Tig <tig@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-09 12:42:34 -07:00

657 lines
22 KiB
C#

using Timeout = Terminal.Gui.App.Timeout;
namespace UnitTests.ViewBaseTests.MouseTests;
[Trait ("Category", "Input")]
public class MouseTests : TestsAllViews
{
// TODO: Add more tests that ensure the above test works with positive adornments
// Test drag to move
[Theory]
[InlineData (0, 0, 0, 0, false)]
[InlineData (0, 0, 0, 4, false)]
[InlineData (1, 0, 0, 4, false)]
[InlineData (0, 1, 0, 4, true)]
[InlineData (0, 0, 1, 4, false)]
[InlineData (1, 1, 0, 3, false)]
[InlineData (1, 1, 0, 4, false)]
[InlineData (1, 1, 0, 5, true)]
[InlineData (1, 1, 0, 6, false)]
[InlineData (1, 1, 0, 11, false)]
[InlineData (1, 1, 0, 12, true)]
[InlineData (1, 1, 0, 13, false)]
[InlineData (1, 1, 0, 14, false)]
[AutoInitShutdown]
public void ButtonPressed_In_Border_Starts_Drag (int marginThickness, int borderThickness, int paddingThickness, int xy, bool expectedMoved)
{
View testView = new ()
{
CanFocus = true,
X = 4,
Y = 4,
Width = 10,
Height = 10,
Arrangement = ViewArrangement.Movable
};
testView.Margin!.Thickness = new (marginThickness);
testView.Border!.Thickness = new (borderThickness);
testView.Padding!.Thickness = new (paddingThickness);
Runnable top = new ();
top.Add (testView);
SessionToken rs = Application.Begin (top);
Assert.Equal (4, testView.Frame.X);
Assert.Equal (new (4, 4), testView.Frame.Location);
Application.RaiseMouseEvent (new () { ScreenPosition = new (xy, xy), Flags = MouseFlags.Button1Pressed });
Application.RaiseMouseEvent (new () { ScreenPosition = new (xy + 1, xy + 1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition });
AutoInitShutdownAttribute.RunIteration ();
Assert.Equal (expectedMoved, new Point (5, 5) == testView.Frame.Location);
// The above grabbed the mouse. Need to ungrab.
Application.Mouse.UngrabMouse ();
top.Dispose ();
}
[Theory]
[InlineData (MouseFlags.Button1Pressed, MouseFlags.Button1Released, MouseFlags.Button1Clicked)]
[InlineData (MouseFlags.Button2Pressed, MouseFlags.Button2Released, MouseFlags.Button2Clicked)]
[InlineData (MouseFlags.Button3Pressed, MouseFlags.Button3Released, MouseFlags.Button3Clicked)]
[InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released, MouseFlags.Button4Clicked)]
public void WantContinuousButtonPressed_False_Button_Press_Release_DoesNotClick (MouseFlags pressed, MouseFlags released, MouseFlags clicked)
{
MouseEventArgs me = new ();
View view = new ()
{
Width = 1,
Height = 1,
WantContinuousButtonPressed = false
};
var clickedCount = 0;
view.MouseEvent += (s, e) => clickedCount += e.IsSingleDoubleOrTripleClicked ? 1 : 0;
me.Flags = pressed;
view.NewMouseEvent (me);
Assert.Equal (0, clickedCount);
me.Handled = false;
me.Flags = pressed;
view.NewMouseEvent (me);
Assert.Equal (0, clickedCount);
me.Handled = false;
me.Flags = released;
view.NewMouseEvent (me);
Assert.Equal (0, clickedCount);
me.Handled = false;
me.Flags = clicked;
view.NewMouseEvent (me);
Assert.Equal (1, clickedCount);
view.Dispose ();
// Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set
Application.ResetState (true);
}
[Theory]
[InlineData (MouseFlags.Button1Clicked)]
[InlineData (MouseFlags.Button2Clicked)]
[InlineData (MouseFlags.Button3Clicked)]
[InlineData (MouseFlags.Button4Clicked)]
public void WantContinuousButtonPressed_True_Button_Clicked_Raises_Activating (MouseFlags clicked)
{
MouseEventArgs me = new ();
View view = new ()
{
Width = 1,
Height = 1,
WantContinuousButtonPressed = true
};
var activatingCount = 0;
view.Activating += (s, e) => activatingCount++;
me.Flags = clicked;
view.NewMouseEvent (me);
Assert.Equal (1, activatingCount);
view.Dispose ();
// Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set
Application.ResetState (true);
}
[Theory]
[InlineData (MouseFlags.Button1Pressed, MouseFlags.Button1Released)]
[InlineData (MouseFlags.Button2Pressed, MouseFlags.Button2Released)]
[InlineData (MouseFlags.Button3Pressed, MouseFlags.Button3Released)]
[InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released)]
public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Button_Press_Release_Clicks (MouseFlags pressed, MouseFlags released)
{
MouseEventArgs me = new ();
View view = new ()
{
Width = 1,
Height = 1,
WantContinuousButtonPressed = true,
WantMousePositionReports = true
};
// Setup components for mouse held down
TimedEvents timed = new ();
MouseGrabHandler grab = new ();
view.MouseHeldDown = new MouseHeldDown (view, timed, grab);
// Register callback for what to do when the mouse is held down
var clickedCount = 0;
view.MouseHeldDown.MouseIsHeldDownTick += (_, _) => clickedCount++;
// Mouse is currently not held down so should be no timers running
Assert.Empty (timed.Timeouts);
// When mouse is held down
me.Flags = pressed;
view.NewMouseEvent (me);
Assert.Equal (0, clickedCount);
me.Handled = false;
// A timer should begin
KeyValuePair<long, Timeout> t = Assert.Single (timed.Timeouts);
// Invoke the timer
t.Value.Callback.Invoke ();
// Event should have been raised
Assert.Equal (1, clickedCount);
Assert.NotEmpty (timed.Timeouts);
// When mouse is released
me.Flags = released;
view.NewMouseEvent (me);
// timer should stop
Assert.Empty (timed.Timeouts);
Assert.Equal (1, clickedCount);
view.Dispose ();
}
[Theory]
[InlineData (MouseFlags.Button1Pressed, MouseFlags.Button1Released, MouseFlags.Button1Clicked)]
[InlineData (MouseFlags.Button2Pressed, MouseFlags.Button2Released, MouseFlags.Button2Clicked)]
[InlineData (MouseFlags.Button3Pressed, MouseFlags.Button3Released, MouseFlags.Button3Clicked)]
[InlineData (MouseFlags.Button4Pressed, MouseFlags.Button4Released, MouseFlags.Button4Clicked)]
public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Button_Press_Release_Clicks_Repeatedly (
MouseFlags pressed,
MouseFlags released,
MouseFlags clicked
)
{
MouseEventArgs me = new ();
View view = new ()
{
Width = 1,
Height = 1,
WantContinuousButtonPressed = true,
WantMousePositionReports = true
};
// Setup components for mouse held down
TimedEvents timed = new ();
MouseGrabHandler grab = new ();
view.MouseHeldDown = new MouseHeldDown (view, timed, grab);
// Register callback for what to do when the mouse is held down
var clickedCount = 0;
view.MouseHeldDown.MouseIsHeldDownTick += (_, _) => clickedCount++;
Assert.Empty (timed.Timeouts);
me.Flags = pressed;
view.NewMouseEvent (me);
Assert.Equal (0, clickedCount);
me.Handled = false;
Assert.NotEmpty (timed.Timeouts);
Assert.Single (timed.Timeouts).Value.Callback.Invoke ();
me.Flags = pressed;
view.NewMouseEvent (me);
Assert.Equal (1, clickedCount);
me.Handled = false;
Assert.NotEmpty (timed.Timeouts);
me.Flags = released;
view.NewMouseEvent (me);
Assert.Equal (1, clickedCount);
me.Handled = false;
Assert.Empty (timed.Timeouts);
me.Flags = clicked;
view.NewMouseEvent (me);
Assert.Equal (1, clickedCount);
view.Dispose ();
}
[Fact]
public void WantContinuousButtonPressed_True_And_WantMousePositionReports_True_Move_InViewport_OutOfViewport_Keeps_Counting ()
{
MouseEventArgs me = new ();
View view = new ()
{
Width = 1,
Height = 1,
WantContinuousButtonPressed = true,
WantMousePositionReports = true
};
// Setup components for mouse held down
TimedEvents timed = new ();
MouseGrabHandler grab = new ();
view.MouseHeldDown = new MouseHeldDown (view, timed, grab);
// Register callback for what to do when the mouse is held down
var clickedCount = 0;
view.MouseHeldDown.MouseIsHeldDownTick += (_, _) => clickedCount++;
// Start in Viewport
me.Flags = MouseFlags.Button1Pressed;
me.Position = me.Position with { X = 0 };
view.NewMouseEvent (me);
Assert.Equal (0, clickedCount);
me.Handled = false;
// Mouse is held down so timer should be ticking
Assert.NotEmpty (timed.Timeouts);
Assert.Equal (0, clickedCount);
// Don't wait, just force it to expire
Assert.Single (timed.Timeouts).Value.Callback.Invoke ();
Assert.Equal (1, clickedCount);
// Move out of Viewport
me.Flags = MouseFlags.Button1Pressed;
me.Position = me.Position with { X = 1 };
view.NewMouseEvent (me);
Assert.Single (timed.Timeouts).Value.Callback.Invoke ();
Assert.Equal (2, clickedCount);
me.Handled = false;
// Move into Viewport
me.Flags = MouseFlags.Button1Pressed;
me.Position = me.Position with { X = 0 };
view.NewMouseEvent (me);
Assert.NotEmpty (timed.Timeouts);
Assert.Equal (2, clickedCount);
me.Handled = false;
// Stay in Viewport
me.Flags = MouseFlags.Button1Pressed;
me.Position = me.Position with { X = 0 };
view.NewMouseEvent (me);
Assert.Single (timed.Timeouts).Value.Callback.Invoke ();
Assert.Equal (3, clickedCount);
me.Handled = false;
view.Dispose ();
}
//[Theory]
//[InlineData (true, MouseState.None, 0, 0, 0, 0)]
//[InlineData (true, MouseState.In, 0, 0, 0, 0)]
//[InlineData (true, MouseState.Pressed, 0, 0, 1, 0)]
//[InlineData (true, MouseState.PressedOutside, 0, 0, 0, 1)]
//public void MouseState_Button1_Pressed_Then_Released_Outside (bool inViewport, MouseState highlightFlags, int noneCount, int expectedInCount, int expectedPressedCount, int expectedPressedOutsideCount)
//{
// MouseEventTestView testView = new ()
// {
// HighlightStates = highlightFlags
// };
// Assert.Equal (0, testView.MouseStateInCount);
// Assert.Equal (0, testView.MouseStatePressedCount);
// Assert.Equal (0, testView.MouseStatePressedOutsideCount);
// Assert.Equal (0, testView.MouseStateNoneCount);
// testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed, Position = new (inViewport ? 0 : 1, 0) });
// Assert.Equal (expectedInCount, testView.MouseStateInCount);
// Assert.Equal (expectedPressedCount, testView.MouseStatePressedCount);
// Assert.Equal (expectedPressedOutsideCount, testView.MouseStatePressedOutsideCount);
// Assert.Equal (noneCount, testView.MouseStateNoneCount);
// testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Released, Position = new (inViewport ? 0 : 1, 0) });
// Assert.Equal (expectedInCount, testView.MouseStateInCount);
// Assert.Equal (expectedPressedCount, testView.MouseStatePressedCount);
// Assert.Equal (expectedPressedOutsideCount, testView.MouseStatePressedOutsideCount);
// Assert.Equal (noneCount, testView.MouseStateNoneCount);
// testView.Dispose ();
// // Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set
// Application.ResetState (true);
//}
// TODO: Add tests for each combination of HighlightFlags
[Theory]
[InlineData (0)]
[InlineData (1)]
[InlineData (10)]
public void MouseState_None_Button1_Pressed_Move_No_Changes (int x)
{
MouseEventTestView testView = new ()
{
HighlightStates = MouseState.None
};
bool inViewport = testView.Viewport.Contains (x, 0);
// Start at 0,0 ; in viewport
testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (0, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
// Move to x,0
testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed, Position = new (x, 0) });
if (inViewport)
{
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (0, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
}
else
{
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (0, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
}
// Move back to 0,0 ; in viewport
testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
if (inViewport)
{
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (0, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
}
else
{
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (0, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
}
testView.Dispose ();
// Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set
Application.ResetState (true);
}
[Theory]
[InlineData (0)]
[InlineData (1)]
[InlineData (10)]
public void MouseState_Pressed_Button1_Pressed_Move_Keeps_Pressed (int x)
{
MouseEventTestView testView = new ()
{
HighlightStates = MouseState.Pressed
};
bool inViewport = testView.Viewport.Contains (x, 0);
// Start at 0,0 ; in viewport
testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (1, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
// Move to x,0
testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed, Position = new (x, 0) });
if (inViewport)
{
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (1, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
}
else
{
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (1, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
}
// Move backto 0,0 ; in viewport
testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
if (inViewport)
{
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (1, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
}
else
{
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (1, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
}
testView.Dispose ();
// Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set
Application.ResetState (true);
}
[Theory]
[InlineData (0)]
[InlineData (1)]
[InlineData (10)]
public void MouseState_PressedOutside_Button1_Pressed_Move_Raises_PressedOutside (int x)
{
MouseEventTestView testView = new ()
{
HighlightStates = MouseState.PressedOutside,
WantContinuousButtonPressed = false
};
bool inViewport = testView.Viewport.Contains (x, 0);
// Start at 0,0 ; in viewport
testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (0, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
// Move to x,0
testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed, Position = new (x, 0) });
if (inViewport)
{
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (0, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
}
else
{
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (0, testView.MouseStatePressedCount);
Assert.Equal (1, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
}
// Move backto 0,0 ; in viewport
testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
if (inViewport)
{
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (0, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
}
else
{
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (0, testView.MouseStatePressedCount);
Assert.Equal (1, testView.MouseStatePressedOutsideCount);
Assert.Equal (1, testView.MouseStateNoneCount);
}
testView.Dispose ();
// Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set
Application.ResetState (true);
}
[Theory]
[InlineData (0)]
[InlineData (1)]
[InlineData (10)]
public void MouseState_PressedOutside_Button1_Pressed_Move_Raises_PressedOutside_WantContinuousButtonPressed (int x)
{
MouseEventTestView testView = new ()
{
HighlightStates = MouseState.PressedOutside,
WantContinuousButtonPressed = true
};
bool inViewport = testView.Viewport.Contains (x, 0);
// Start at 0,0 ; in viewport
testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (0, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
// Move to x,0
testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed, Position = new (x, 0) });
if (inViewport)
{
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (0, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
}
else
{
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (0, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
}
// Move backto 0,0 ; in viewport
testView.NewMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
if (inViewport)
{
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (0, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
}
else
{
Assert.Equal (0, testView.MouseStateInCount);
Assert.Equal (0, testView.MouseStatePressedCount);
Assert.Equal (0, testView.MouseStatePressedOutsideCount);
Assert.Equal (0, testView.MouseStateNoneCount);
}
testView.Dispose ();
// Button1Pressed, Button1Released cause Application.Mouse.MouseGrabView to be set
Application.ResetState (true);
}
private class MouseEventTestView : View
{
public int MouseEnterCount { get; private set; }
public int MouseLeaveCount { get; private set; }
public int MouseStatePressedOutsideCount { get; private set; }
public int MouseStateInCount { get; private set; }
public int MouseStatePressedCount { get; private set; }
public int MouseStateNoneCount { get; private set; }
public MouseEventTestView ()
{
Height = 1;
Width = 1;
CanFocus = true;
Id = "mouseEventTestView";
MouseLeave += (s, e) => { MouseEnterCount++; };
MouseEnter += (s, e) => { MouseLeaveCount++; };
}
/// <inheritdoc/>
protected override void OnMouseStateChanged (EventArgs<MouseState> args)
{
switch (args.Value)
{
case MouseState.None:
MouseStateNoneCount++;
break;
case MouseState.In:
MouseStateInCount++;
break;
case MouseState.Pressed:
MouseStatePressedCount++;
break;
case MouseState.PressedOutside:
MouseStatePressedOutsideCount++;
break;
}
base.OnMouseStateChanged (args);
}
}
}