mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2026-02-10 04:03:41 +01:00
Cleanup
This commit is contained in:
@@ -1,323 +0,0 @@
|
||||
// Claude - Opus 4.5
|
||||
// Tests for MouseHighlightStates mouse event interception bug
|
||||
// https://github.com/gui-cs/Terminal.Gui/issues/[issue-number]
|
||||
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace UnitTests.ViewBaseTests.MouseTests;
|
||||
|
||||
[TestSubject (typeof (View))]
|
||||
[Trait ("Category", "Input")]
|
||||
public class MouseHighlightStatesSubViewTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests that when a SuperView has MouseHighlightStates = MouseState.In,
|
||||
/// clicking on a SubView should route the event to the SubView, not the SuperView.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[AutoInitShutdown]
|
||||
[InlineData (MouseState.In)]
|
||||
[InlineData (MouseState.Pressed)]
|
||||
[InlineData (MouseState.In | MouseState.Pressed)]
|
||||
public void MouseHighlightStates_DoesNotIntercept_SubView_Events (MouseState highlightState)
|
||||
{
|
||||
// Arrange
|
||||
var superViewActivateCount = 0;
|
||||
var subViewActivateCount = 0;
|
||||
|
||||
View superView = new ()
|
||||
{
|
||||
Id = "superView",
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = 10,
|
||||
Height = 10,
|
||||
MouseHighlightStates = highlightState
|
||||
};
|
||||
|
||||
superView.Activating += (s, e) => { superViewActivateCount++; };
|
||||
|
||||
View subView = new ()
|
||||
{
|
||||
Id = "subView",
|
||||
X = 2,
|
||||
Y = 2,
|
||||
Width = 5,
|
||||
Height = 5,
|
||||
CanFocus = true
|
||||
};
|
||||
|
||||
subView.Activating += (s, e) => { subViewActivateCount++; };
|
||||
|
||||
superView.Add (subView);
|
||||
|
||||
Runnable top = new ();
|
||||
top.Add (superView);
|
||||
|
||||
SessionToken rs = Application.Begin (top);
|
||||
|
||||
// Act: Click on the SubView
|
||||
// SubView is at screen position (2, 2) relative to SuperView at (0, 0)
|
||||
Application.RaiseMouseEvent (new () { ScreenPosition = new (2, 2), Flags = MouseFlags.LeftButtonPressed });
|
||||
Application.RaiseMouseEvent (new () { ScreenPosition = new (2, 2), Flags = MouseFlags.LeftButtonReleased });
|
||||
Application.RaiseMouseEvent (new () { ScreenPosition = new (2, 2), Flags = MouseFlags.LeftButtonClicked });
|
||||
|
||||
// Need to process the event
|
||||
AutoInitShutdownAttribute.RunIteration ();
|
||||
|
||||
// Assert: SubView should receive the event, not SuperView
|
||||
Assert.Equal (1, subViewActivateCount);
|
||||
Assert.Equal (0, superViewActivateCount);
|
||||
|
||||
// Cleanup
|
||||
Application.Mouse.UngrabMouse ();
|
||||
top.Dispose ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that when a SuperView has MouseHighlightStates = MouseState.None (default),
|
||||
/// clicking on a SubView correctly routes the event to the SubView.
|
||||
/// This is the baseline behavior that should always work.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
[AutoInitShutdown]
|
||||
public void MouseHighlightStates_None_DoesNotIntercept_SubView_Events ()
|
||||
{
|
||||
// Arrange
|
||||
var superViewActivateCount = 0;
|
||||
var subViewActivateCount = 0;
|
||||
|
||||
View superView = new ()
|
||||
{
|
||||
Id = "superView",
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = 10,
|
||||
Height = 10,
|
||||
MouseHighlightStates = MouseState.None // Explicit none
|
||||
};
|
||||
|
||||
superView.Activating += (s, e) => { superViewActivateCount++; };
|
||||
|
||||
View subView = new ()
|
||||
{
|
||||
Id = "subView",
|
||||
X = 2,
|
||||
Y = 2,
|
||||
Width = 5,
|
||||
Height = 5,
|
||||
CanFocus = true
|
||||
};
|
||||
|
||||
subView.Activating += (s, e) => { subViewActivateCount++; };
|
||||
|
||||
superView.Add (subView);
|
||||
|
||||
Runnable top = new ();
|
||||
top.Add (superView);
|
||||
|
||||
SessionToken rs = Application.Begin (top);
|
||||
|
||||
// Act: Click on the SubView
|
||||
Application.RaiseMouseEvent (new () { ScreenPosition = new (2, 2), Flags = MouseFlags.LeftButtonPressed });
|
||||
Application.RaiseMouseEvent (new () { ScreenPosition = new (2, 2), Flags = MouseFlags.LeftButtonReleased });
|
||||
Application.RaiseMouseEvent (new () { ScreenPosition = new (2, 2), Flags = MouseFlags.LeftButtonClicked });
|
||||
|
||||
AutoInitShutdownAttribute.RunIteration ();
|
||||
|
||||
// Assert: SubView should receive the event
|
||||
Assert.Equal (1, subViewActivateCount);
|
||||
// SuperView may receive it via command bubbling, which is expected behavior
|
||||
// Assert.Equal (0, superViewActivateCount);
|
||||
|
||||
// Cleanup
|
||||
top.Dispose ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that when clicking on the SuperView (not on a SubView),
|
||||
/// the SuperView correctly receives the event even with MouseHighlightStates set.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[AutoInitShutdown]
|
||||
[InlineData (MouseState.In)]
|
||||
[InlineData (MouseState.Pressed)]
|
||||
public void MouseHighlightStates_SuperView_Receives_Events_When_Not_On_SubView (MouseState highlightState)
|
||||
{
|
||||
// Arrange
|
||||
var superViewActivateCount = 0;
|
||||
var subViewActivateCount = 0;
|
||||
|
||||
View superView = new ()
|
||||
{
|
||||
Id = "superView",
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = 10,
|
||||
Height = 10,
|
||||
MouseHighlightStates = highlightState
|
||||
};
|
||||
|
||||
superView.Activating += (s, e) => { superViewActivateCount++; };
|
||||
|
||||
View subView = new ()
|
||||
{
|
||||
Id = "subView",
|
||||
X = 2,
|
||||
Y = 2,
|
||||
Width = 5,
|
||||
Height = 5
|
||||
};
|
||||
|
||||
subView.Activating += (s, e) => { subViewActivateCount++; };
|
||||
|
||||
superView.Add (subView);
|
||||
|
||||
Runnable top = new ();
|
||||
top.Add (superView);
|
||||
|
||||
SessionToken rs = Application.Begin (top);
|
||||
|
||||
// Act: Click on the SuperView (position 8,8 is outside the SubView which is at 2,2 with size 5x5)
|
||||
Application.RaiseMouseEvent (new () { ScreenPosition = new (8, 8), Flags = MouseFlags.LeftButtonPressed });
|
||||
Application.RaiseMouseEvent (new () { ScreenPosition = new (8, 8), Flags = MouseFlags.LeftButtonReleased });
|
||||
Application.RaiseMouseEvent (new () { ScreenPosition = new (8, 8), Flags = MouseFlags.LeftButtonClicked });
|
||||
|
||||
AutoInitShutdownAttribute.RunIteration ();
|
||||
|
||||
// Assert: SuperView should receive the event
|
||||
Assert.Equal (1, superViewActivateCount);
|
||||
Assert.Equal (0, subViewActivateCount);
|
||||
|
||||
// Cleanup
|
||||
Application.Mouse.UngrabMouse ();
|
||||
top.Dispose ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the specific Shortcut scenario mentioned in the issue.
|
||||
/// When a Shortcut has MouseHighlightStates = MouseState.In (old default),
|
||||
/// clicking on the CommandView should route the event to CommandView.
|
||||
/// Note: This test is currently disabled as Shortcut has a complex layout
|
||||
/// and the basic fix works for simple view hierarchies.
|
||||
/// </summary>
|
||||
[Fact (Skip = "Shortcut has complex layout - core fix works for basic SubView scenarios")]
|
||||
[AutoInitShutdown]
|
||||
public void Shortcut_With_MouseHighlightStates_In_Routes_To_CommandView ()
|
||||
{
|
||||
// Arrange
|
||||
var shortcutActivatingCount = 0;
|
||||
var checkBoxCheckedCount = 0;
|
||||
|
||||
Shortcut shortcut = new ()
|
||||
{
|
||||
Key = Key.F1,
|
||||
Title = "Test",
|
||||
CommandView = new CheckBox { Text = "Enable Feature" },
|
||||
MouseHighlightStates = MouseState.In // Explicitly set to old default to test the bug
|
||||
};
|
||||
|
||||
shortcut.Activating += (s, e) => { shortcutActivatingCount++; };
|
||||
|
||||
CheckBox checkBox = (CheckBox)shortcut.CommandView;
|
||||
checkBox.ValueChanged += (s, e) => { checkBoxCheckedCount++; };
|
||||
|
||||
Runnable top = new ();
|
||||
top.Add (shortcut);
|
||||
|
||||
SessionToken rs = Application.Begin (top);
|
||||
|
||||
// Get the screen position of the CommandView
|
||||
Rectangle commandViewScreenRect = shortcut.CommandView.FrameToScreen ();
|
||||
Point commandViewScreenPos = commandViewScreenRect.Location;
|
||||
|
||||
// Act: Click on the CommandView (CheckBox)
|
||||
Application.RaiseMouseEvent (new () { ScreenPosition = commandViewScreenPos, Flags = MouseFlags.LeftButtonPressed });
|
||||
Application.RaiseMouseEvent (new () { ScreenPosition = commandViewScreenPos, Flags = MouseFlags.LeftButtonReleased });
|
||||
Application.RaiseMouseEvent (new () { ScreenPosition = commandViewScreenPos, Flags = MouseFlags.LeftButtonClicked });
|
||||
|
||||
AutoInitShutdownAttribute.RunIteration ();
|
||||
|
||||
// Assert: The checkbox should be toggled when clicking on it
|
||||
// The shortcut activation is a consequence of the CheckBox forwarding the event
|
||||
Assert.Equal (1, checkBoxCheckedCount);
|
||||
|
||||
// Cleanup
|
||||
Application.Mouse.UngrabMouse ();
|
||||
top.Dispose ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that nested views (SuperView with MouseHighlightStates, SubView with MouseHighlightStates)
|
||||
/// route events to the deepest view under the mouse.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
[AutoInitShutdown]
|
||||
public void MouseHighlightStates_Nested_Routes_To_Deepest_View ()
|
||||
{
|
||||
// Arrange
|
||||
var superViewActivateCount = 0;
|
||||
var subView1ActivateCount = 0;
|
||||
var subView2ActivateCount = 0;
|
||||
|
||||
View superView = new ()
|
||||
{
|
||||
Id = "superView",
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = 20,
|
||||
Height = 20,
|
||||
MouseHighlightStates = MouseState.In
|
||||
};
|
||||
|
||||
superView.Activating += (s, e) => { superViewActivateCount++; };
|
||||
|
||||
View subView1 = new ()
|
||||
{
|
||||
Id = "subView1",
|
||||
X = 5,
|
||||
Y = 5,
|
||||
Width = 10,
|
||||
Height = 10,
|
||||
MouseHighlightStates = MouseState.In
|
||||
};
|
||||
|
||||
subView1.Activating += (s, e) => { subView1ActivateCount++; };
|
||||
|
||||
View subView2 = new ()
|
||||
{
|
||||
Id = "subView2",
|
||||
X = 2,
|
||||
Y = 2,
|
||||
Width = 5,
|
||||
Height = 5,
|
||||
CanFocus = true
|
||||
};
|
||||
|
||||
subView2.Activating += (s, e) => { subView2ActivateCount++; };
|
||||
|
||||
superView.Add (subView1);
|
||||
subView1.Add (subView2);
|
||||
|
||||
Runnable top = new ();
|
||||
top.Add (superView);
|
||||
|
||||
SessionToken rs = Application.Begin (top);
|
||||
|
||||
// Act: Click on subView2 (screen position is SuperView(0,0) + subView1(5,5) + subView2(2,2) = 7,7)
|
||||
Application.RaiseMouseEvent (new () { ScreenPosition = new (7, 7), Flags = MouseFlags.LeftButtonPressed });
|
||||
Application.RaiseMouseEvent (new () { ScreenPosition = new (7, 7), Flags = MouseFlags.LeftButtonReleased });
|
||||
Application.RaiseMouseEvent (new () { ScreenPosition = new (7, 7), Flags = MouseFlags.LeftButtonClicked });
|
||||
|
||||
AutoInitShutdownAttribute.RunIteration ();
|
||||
|
||||
// Assert: Only the deepest view (subView2) should receive the event
|
||||
Assert.Equal (1, subView2ActivateCount);
|
||||
Assert.Equal (0, subView1ActivateCount);
|
||||
Assert.Equal (0, superViewActivateCount);
|
||||
|
||||
// Cleanup
|
||||
Application.Mouse.UngrabMouse ();
|
||||
top.Dispose ();
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,15 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
app.Init (DriverRegistry.Names.ANSI);
|
||||
|
||||
using Runnable runnable = new ();
|
||||
View view = new () { X = 0, Y = 0, Width = 10, Height = 10, MouseHighlightStates = mouseState };
|
||||
|
||||
View view = new ()
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = 10,
|
||||
Height = 10,
|
||||
MouseHighlightStates = mouseState
|
||||
};
|
||||
|
||||
List<MouseFlags> receivedFlags = [];
|
||||
view.MouseEvent += MouseEventHandler;
|
||||
@@ -29,10 +37,12 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
IInputInjector injector = app.GetInputInjector ();
|
||||
|
||||
// First click at T+0
|
||||
injector.InjectMouse (new () { ScreenPosition = new (5, 5), Flags = MouseFlags.LeftButtonPressed, Timestamp = baseTime }, options);
|
||||
injector.InjectMouse (new Mouse { ScreenPosition = new Point (5, 5), Flags = MouseFlags.LeftButtonPressed, Timestamp = baseTime }, options);
|
||||
|
||||
injector.InjectMouse (
|
||||
new () { ScreenPosition = new (5, 5), Flags = MouseFlags.LeftButtonReleased, Timestamp = baseTime.AddMilliseconds (100) },
|
||||
injector.InjectMouse (new Mouse
|
||||
{
|
||||
ScreenPosition = new Point (5, 5), Flags = MouseFlags.LeftButtonReleased, Timestamp = baseTime.AddMilliseconds (100)
|
||||
},
|
||||
options);
|
||||
|
||||
// Assert
|
||||
@@ -45,7 +55,7 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
|
||||
return;
|
||||
|
||||
void MouseEventHandler (object? s, Mouse e) { receivedFlags.Add (e.Flags); }
|
||||
void MouseEventHandler (object? s, Mouse e) => receivedFlags.Add (e.Flags);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -56,7 +66,15 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
app.Init (DriverRegistry.Names.ANSI);
|
||||
|
||||
using Runnable runnable = new ();
|
||||
View view = new () { X = 0, Y = 0, Width = 10, Height = 10, MouseHighlightStates = mouseState };
|
||||
|
||||
View view = new ()
|
||||
{
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = 10,
|
||||
Height = 10,
|
||||
MouseHighlightStates = mouseState
|
||||
};
|
||||
|
||||
var activateCount = 0;
|
||||
view.Activating += (_, _) => activateCount++;
|
||||
@@ -72,10 +90,12 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
IInputInjector injector = app.GetInputInjector ();
|
||||
|
||||
// First click at T+0
|
||||
injector.InjectMouse (new () { ScreenPosition = new (5, 5), Flags = MouseFlags.LeftButtonPressed, Timestamp = baseTime }, options);
|
||||
injector.InjectMouse (new Mouse { ScreenPosition = new Point (5, 5), Flags = MouseFlags.LeftButtonPressed, Timestamp = baseTime }, options);
|
||||
|
||||
injector.InjectMouse (
|
||||
new () { ScreenPosition = new (5, 5), Flags = MouseFlags.LeftButtonReleased, Timestamp = baseTime.AddMilliseconds (100) },
|
||||
injector.InjectMouse (new Mouse
|
||||
{
|
||||
ScreenPosition = new Point (5, 5), Flags = MouseFlags.LeftButtonReleased, Timestamp = baseTime.AddMilliseconds (100)
|
||||
},
|
||||
options);
|
||||
|
||||
// Assert
|
||||
@@ -94,7 +114,7 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
Attribute highlight = new (ColorName16.Blue, ColorName16.Black, TextStyle.Italic);
|
||||
|
||||
Runnable superview = new () { Width = Dim.Fill (), Height = Dim.Fill () };
|
||||
superview.SetScheme (new () { Focus = focus, Highlight = highlight });
|
||||
superview.SetScheme (new Scheme { Focus = focus, Highlight = highlight });
|
||||
View view = new () { Width = Dim.Fill (), Height = Dim.Fill (), Text = "| Hi |", MouseHighlightStates = MouseState.In };
|
||||
superview.Add (view);
|
||||
|
||||
@@ -107,7 +127,7 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
|
||||
DriverAssert.AssertDriverContentsAre ("| Hi |", output, app.Driver);
|
||||
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (2, 0), Flags = MouseFlags.PositionReport });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (2, 0), Flags = MouseFlags.PositionReport });
|
||||
app.LayoutAndDraw ();
|
||||
|
||||
for (var i = 0; i < app.Driver?.Cols; i++)
|
||||
@@ -132,7 +152,7 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
Attribute highlight = new (ColorName16.Blue, ColorName16.Black, TextStyle.Italic);
|
||||
|
||||
Runnable superview = new () { Width = Dim.Fill (), Height = Dim.Fill () };
|
||||
superview.SetScheme (new () { Focus = focus, Highlight = highlight });
|
||||
superview.SetScheme (new Scheme { Focus = focus, Highlight = highlight });
|
||||
View view = new () { Width = Dim.Fill (), Height = Dim.Fill (), Text = "| Hi |", MouseHighlightStates = MouseState.In };
|
||||
superview.Add (view);
|
||||
|
||||
@@ -142,7 +162,7 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
Attribute highlight2 = new (ColorName16.Red, ColorName16.Yellow, TextStyle.Italic);
|
||||
|
||||
Runnable modalSuperview = new () { Y = 1, Width = 9, Height = 4, BorderStyle = LineStyle.Single };
|
||||
modalSuperview.SetScheme (new () { Normal = normal, Highlight = highlight2 });
|
||||
modalSuperview.SetScheme (new Scheme { Normal = normal, Highlight = highlight2 });
|
||||
View view2 = new () { Width = Dim.Fill (), Height = Dim.Fill (), Text = "| Hey |", MouseHighlightStates = MouseState.In };
|
||||
modalSuperview.Add (view2);
|
||||
|
||||
@@ -158,8 +178,7 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
Assert.Equal (normal, app.Driver.Contents? [2, i].Attribute);
|
||||
}
|
||||
|
||||
DriverAssert.AssertDriverContentsAre (
|
||||
"""
|
||||
DriverAssert.AssertDriverContentsAre ("""
|
||||
| Hi |
|
||||
┌───────┐
|
||||
│| Hey |│
|
||||
@@ -169,7 +188,7 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
output,
|
||||
app.Driver);
|
||||
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (2, 2), Flags = MouseFlags.PositionReport });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (2, 2), Flags = MouseFlags.PositionReport });
|
||||
app.LayoutAndDraw ();
|
||||
|
||||
for (var i = 0; i < app.Driver?.Cols; i++)
|
||||
@@ -182,8 +201,7 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
Assert.Equal (highlight2, app.Driver?.Contents? [2, i].Attribute);
|
||||
}
|
||||
|
||||
DriverAssert.AssertDriverContentsAre (
|
||||
"""
|
||||
DriverAssert.AssertDriverContentsAre ("""
|
||||
| Hi |
|
||||
┌───────┐
|
||||
│| Hey |│
|
||||
@@ -239,7 +257,7 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
Assert.Equal (MouseState.None, view2.MouseState);
|
||||
|
||||
// Press mouse button on view1
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (5, 0), Flags = MouseFlags.LeftButtonPressed });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (5, 0), Flags = MouseFlags.LeftButtonPressed });
|
||||
|
||||
// view1 should have Pressed state (may also have In)
|
||||
Assert.True (view1.MouseState.HasFlag (MouseState.Pressed));
|
||||
@@ -249,28 +267,28 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
view2States.Clear ();
|
||||
|
||||
// Move mouse over view2 while still holding the button down
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (15, 0), Flags = MouseFlags.PositionReport });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (15, 0), Flags = MouseFlags.PositionReport });
|
||||
|
||||
// view2 should NOT be highlighted
|
||||
Assert.Equal (MouseState.None, view2.MouseState);
|
||||
Assert.Empty (view2States);
|
||||
|
||||
// Move mouse within view2 to a different position (still holding button)
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (20, 1), Flags = MouseFlags.PositionReport });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (20, 1), Flags = MouseFlags.PositionReport });
|
||||
|
||||
// CRITICAL: view2 should STILL not be highlighted - this will fail with current implementation
|
||||
Assert.Equal (MouseState.None, view2.MouseState);
|
||||
Assert.Empty (view2States);
|
||||
|
||||
// Move mouse out of view2 (into empty space)
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (5, 2), Flags = MouseFlags.PositionReport });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (5, 2), Flags = MouseFlags.PositionReport });
|
||||
|
||||
// view2 should still be None
|
||||
Assert.Equal (MouseState.None, view2.MouseState);
|
||||
Assert.Empty (view2States);
|
||||
|
||||
// Move back into view2 at different position (still holding button)
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (25, 2), Flags = MouseFlags.PositionReport });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (25, 2), Flags = MouseFlags.PositionReport });
|
||||
|
||||
// CRITICAL: view2 should STILL not be highlighted - this will fail with current implementation
|
||||
Assert.Equal (MouseState.None, view2.MouseState);
|
||||
@@ -324,7 +342,7 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
Assert.Equal (MouseState.None, view2.MouseState);
|
||||
|
||||
// Press mouse button on view1
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (5, 0), Flags = MouseFlags.LeftButtonPressed });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (5, 0), Flags = MouseFlags.LeftButtonPressed });
|
||||
|
||||
// view1 should have Pressed state (may also have In)
|
||||
Assert.True (view1.MouseState.HasFlag (MouseState.Pressed));
|
||||
@@ -334,28 +352,28 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
view2States.Clear ();
|
||||
|
||||
// Move mouse over view2 while still holding the button down
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (15, 0), Flags = MouseFlags.PositionReport });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (15, 0), Flags = MouseFlags.PositionReport });
|
||||
|
||||
// view2 should NOT be highlighted
|
||||
Assert.Equal (MouseState.None, view2.MouseState);
|
||||
Assert.Empty (view2States);
|
||||
|
||||
// Move mouse within view2 to a different position (still holding button)
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (20, 1), Flags = MouseFlags.PositionReport });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (20, 1), Flags = MouseFlags.PositionReport });
|
||||
|
||||
// CRITICAL: view2 should STILL not be highlighted - this will fail with current implementation
|
||||
Assert.Equal (MouseState.None, view2.MouseState);
|
||||
Assert.Empty (view2States);
|
||||
|
||||
// Move mouse out of view2 (into empty space)
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (5, 2), Flags = MouseFlags.PositionReport });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (5, 2), Flags = MouseFlags.PositionReport });
|
||||
|
||||
// view2 should still be None
|
||||
Assert.Equal (MouseState.None, view2.MouseState);
|
||||
Assert.Empty (view2States);
|
||||
|
||||
// Move back into view2 at different position (still holding button)
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (25, 2), Flags = MouseFlags.PositionReport });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (25, 2), Flags = MouseFlags.PositionReport });
|
||||
|
||||
// CRITICAL: view2 should STILL not be highlighted - this will fail with current implementation
|
||||
Assert.Equal (MouseState.None, view2.MouseState);
|
||||
@@ -405,20 +423,20 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
app.Begin (runnable);
|
||||
|
||||
// Press on view1
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (5, 0), Flags = MouseFlags.LeftButtonPressed });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (5, 0), Flags = MouseFlags.LeftButtonPressed });
|
||||
|
||||
// Drag to view2
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (15, 0), Flags = MouseFlags.PositionReport });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (15, 0), Flags = MouseFlags.PositionReport });
|
||||
|
||||
// view2 should not be highlighted during drag
|
||||
Assert.Equal (MouseState.None, view2.MouseState);
|
||||
Assert.Empty (view2States);
|
||||
|
||||
// Release button while over view2
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (15, 0), Flags = MouseFlags.LeftButtonReleased });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (15, 0), Flags = MouseFlags.LeftButtonReleased });
|
||||
|
||||
// Send Clicked event (this is what triggers ungrab)
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (15, 0), Flags = MouseFlags.LeftButtonClicked });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (15, 0), Flags = MouseFlags.LeftButtonClicked });
|
||||
|
||||
// After clicked (which ungrabs), view2 should get MouseState.In (mouse is now over it and no button is pressed)
|
||||
Assert.Equal (MouseState.In, view2.MouseState);
|
||||
@@ -477,7 +495,7 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
app.Begin (runnable);
|
||||
|
||||
// Press on view1
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (5, 0), Flags = MouseFlags.LeftButtonPressed });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (5, 0), Flags = MouseFlags.LeftButtonPressed });
|
||||
|
||||
// Verify view1 grabbed the mouse
|
||||
Assert.True (app.Mouse.IsGrabbed (view1));
|
||||
@@ -487,7 +505,7 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
view2EnterCalled = false;
|
||||
|
||||
// Drag to view2 position (15, 0) - button still held
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (15, 0), Flags = MouseFlags.PositionReport });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (15, 0), Flags = MouseFlags.PositionReport });
|
||||
|
||||
// CRITICAL: view2 should NOT receive MouseEnter event
|
||||
Assert.False (view2EnterCalled, "view2 received MouseEnter event during drag from view1");
|
||||
@@ -495,7 +513,7 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
Assert.Equal (MouseState.None, view2.MouseState);
|
||||
|
||||
// Move WITHIN view2 to position (15, 1) - still holding button
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (15, 1), Flags = MouseFlags.PositionReport });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (15, 1), Flags = MouseFlags.PositionReport });
|
||||
|
||||
// CRITICAL: view2 should STILL not receive any events
|
||||
Assert.False (view2EnterCalled, "view2 received MouseEnter event while dragging within it");
|
||||
@@ -503,7 +521,7 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
Assert.Equal (MouseState.None, view2.MouseState);
|
||||
|
||||
// Move OUT of view2 back to view1 area - still holding button
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (5, 0), Flags = MouseFlags.PositionReport });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (5, 0), Flags = MouseFlags.PositionReport });
|
||||
|
||||
// view2 should still have no events (it was never "in" so no "leave")
|
||||
Assert.False (view2EnterCalled);
|
||||
@@ -511,7 +529,7 @@ public class HighlightStatesTests (ITestOutputHelper output)
|
||||
Assert.Equal (MouseState.None, view2.MouseState);
|
||||
|
||||
// Move BACK into view2 - still holding button
|
||||
app.Mouse.RaiseMouseEvent (new () { ScreenPosition = new (15, 0), Flags = MouseFlags.PositionReport });
|
||||
app.Mouse.RaiseMouseEvent (new Mouse { ScreenPosition = new Point (15, 0), Flags = MouseFlags.PositionReport });
|
||||
|
||||
// CRITICAL: view2 should STILL not receive any events
|
||||
Assert.False (view2EnterCalled, "view2 received MouseEnter event when re-entering during drag");
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
// Claude - Sonnet 4.5
|
||||
// Tests for MouseHighlightStates mouse event routing to SubViews
|
||||
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ViewBaseTests.MouseTests;
|
||||
|
||||
[TestSubject (typeof (View))]
|
||||
[Trait ("Category", "Input")]
|
||||
[Trait ("Category", "Mouse")]
|
||||
public class MouseHighlightStatesSubViewTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests that when a SuperView has MouseHighlightStates set,
|
||||
/// clicking on a SubView should route the event to the SubView, not the SuperView.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData (MouseState.In)]
|
||||
[InlineData (MouseState.Pressed)]
|
||||
[InlineData (MouseState.In | MouseState.Pressed)]
|
||||
public void MouseHighlightStates_DoesNotIntercept_SubView_Events (MouseState highlightState)
|
||||
{
|
||||
// Arrange
|
||||
VirtualTimeProvider time = new ();
|
||||
using IApplication app = Application.Create (time);
|
||||
app.Init (DriverRegistry.Names.ANSI);
|
||||
|
||||
var superViewActivateCount = 0;
|
||||
var subViewActivateCount = 0;
|
||||
|
||||
Runnable runnable = new ();
|
||||
|
||||
View superView = new ()
|
||||
{
|
||||
Id = "superView",
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = 10,
|
||||
Height = 10,
|
||||
MouseHighlightStates = highlightState
|
||||
};
|
||||
|
||||
superView.Activating += (_, _) => { superViewActivateCount++; };
|
||||
|
||||
View subView = new ()
|
||||
{
|
||||
Id = "subView",
|
||||
X = 2,
|
||||
Y = 2,
|
||||
Width = 5,
|
||||
Height = 5,
|
||||
CanFocus = true
|
||||
};
|
||||
|
||||
subView.Activating += (_, _) => { subViewActivateCount++; };
|
||||
|
||||
superView.Add (subView);
|
||||
runnable.Add (superView);
|
||||
app.Begin (runnable);
|
||||
|
||||
// Act: Click on the SubView at screen position (2, 2)
|
||||
app.InjectMouse (new Mouse { ScreenPosition = new Point (2, 2), Flags = MouseFlags.LeftButtonPressed });
|
||||
app.InjectMouse (new Mouse { ScreenPosition = new Point (2, 2), Flags = MouseFlags.LeftButtonReleased });
|
||||
|
||||
// Assert: SubView should receive the event, not SuperView
|
||||
Assert.Equal (1, subViewActivateCount);
|
||||
Assert.Equal (0, superViewActivateCount);
|
||||
|
||||
runnable.Dispose ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that when a SuperView has MouseHighlightStates = MouseState.None (default),
|
||||
/// clicking on a SubView correctly routes the event to the SubView.
|
||||
/// This is the baseline behavior that should always work.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MouseHighlightStates_None_DoesNotIntercept_SubView_Events ()
|
||||
{
|
||||
// Arrange
|
||||
VirtualTimeProvider time = new ();
|
||||
using IApplication app = Application.Create (time);
|
||||
app.Init (DriverRegistry.Names.ANSI);
|
||||
|
||||
var superViewActivateCount = 0;
|
||||
var subViewActivateCount = 0;
|
||||
|
||||
Runnable runnable = new ();
|
||||
|
||||
View superView = new ()
|
||||
{
|
||||
Id = "superView",
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = 10,
|
||||
Height = 10,
|
||||
MouseHighlightStates = MouseState.None
|
||||
};
|
||||
|
||||
superView.Activating += (_, _) => { superViewActivateCount++; };
|
||||
|
||||
View subView = new ()
|
||||
{
|
||||
Id = "subView",
|
||||
X = 2,
|
||||
Y = 2,
|
||||
Width = 5,
|
||||
Height = 5,
|
||||
CanFocus = true
|
||||
};
|
||||
|
||||
subView.Activating += (_, _) => { subViewActivateCount++; };
|
||||
|
||||
superView.Add (subView);
|
||||
runnable.Add (superView);
|
||||
app.Begin (runnable);
|
||||
|
||||
// Act: Click on the SubView
|
||||
app.InjectMouse (new Mouse { ScreenPosition = new Point (2, 2), Flags = MouseFlags.LeftButtonPressed });
|
||||
app.InjectMouse (new Mouse { ScreenPosition = new Point (2, 2), Flags = MouseFlags.LeftButtonReleased });
|
||||
|
||||
// Assert: SubView should receive the event
|
||||
Assert.Equal (1, subViewActivateCount);
|
||||
|
||||
// SuperView may receive it via command bubbling, which is expected behavior
|
||||
|
||||
runnable.Dispose ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that when clicking on the SuperView (not on a SubView),
|
||||
/// the SuperView correctly receives the event even with MouseHighlightStates set.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData (MouseState.In)]
|
||||
[InlineData (MouseState.Pressed)]
|
||||
public void MouseHighlightStates_SuperView_Receives_Events_When_Not_On_SubView (MouseState highlightState)
|
||||
{
|
||||
// Arrange
|
||||
VirtualTimeProvider time = new ();
|
||||
using IApplication app = Application.Create (time);
|
||||
app.Init (DriverRegistry.Names.ANSI);
|
||||
|
||||
var superViewActivateCount = 0;
|
||||
var subViewActivateCount = 0;
|
||||
|
||||
Runnable runnable = new ();
|
||||
|
||||
View superView = new ()
|
||||
{
|
||||
Id = "superView",
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = 10,
|
||||
Height = 10,
|
||||
MouseHighlightStates = highlightState
|
||||
};
|
||||
|
||||
superView.Activating += (_, _) => { superViewActivateCount++; };
|
||||
|
||||
View subView = new ()
|
||||
{
|
||||
Id = "subView",
|
||||
X = 2,
|
||||
Y = 2,
|
||||
Width = 5,
|
||||
Height = 5
|
||||
};
|
||||
|
||||
subView.Activating += (_, _) => { subViewActivateCount++; };
|
||||
|
||||
superView.Add (subView);
|
||||
runnable.Add (superView);
|
||||
app.Begin (runnable);
|
||||
|
||||
// Act: Click on the SuperView at (8,8), outside the SubView (at 2,2 with size 5x5)
|
||||
app.InjectMouse (new Mouse { ScreenPosition = new Point (8, 8), Flags = MouseFlags.LeftButtonPressed });
|
||||
app.InjectMouse (new Mouse { ScreenPosition = new Point (8, 8), Flags = MouseFlags.LeftButtonReleased });
|
||||
|
||||
// Assert: SuperView should receive the event
|
||||
Assert.Equal (1, superViewActivateCount);
|
||||
Assert.Equal (0, subViewActivateCount);
|
||||
|
||||
runnable.Dispose ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the specific Shortcut scenario mentioned in the issue.
|
||||
/// When a Shortcut has MouseHighlightStates = MouseState.In (old default),
|
||||
/// clicking on the CommandView should route the event to CommandView.
|
||||
/// </summary>
|
||||
[Fact (Skip = "Shortcut has complex layout - core fix works for basic SubView scenarios")]
|
||||
public void Shortcut_With_MouseHighlightStates_In_Routes_To_CommandView ()
|
||||
{
|
||||
// Arrange
|
||||
VirtualTimeProvider time = new ();
|
||||
using IApplication app = Application.Create (time);
|
||||
app.Init (DriverRegistry.Names.ANSI);
|
||||
|
||||
var checkBoxCheckedCount = 0;
|
||||
|
||||
Shortcut shortcut = new ()
|
||||
{
|
||||
Key = Key.F1, Title = "Test", CommandView = new CheckBox { Text = "Enable Feature" }, MouseHighlightStates = MouseState.In
|
||||
};
|
||||
|
||||
var checkBox = (CheckBox)shortcut.CommandView;
|
||||
checkBox.ValueChanged += (_, _) => { checkBoxCheckedCount++; };
|
||||
|
||||
Runnable runnable = new ();
|
||||
runnable.Add (shortcut);
|
||||
app.Begin (runnable);
|
||||
|
||||
// Get the screen position of the CommandView
|
||||
Rectangle commandViewScreenRect = shortcut.CommandView.FrameToScreen ();
|
||||
Point commandViewScreenPos = commandViewScreenRect.Location;
|
||||
|
||||
// Act: Click on the CommandView (CheckBox)
|
||||
app.InjectMouse (new Mouse { ScreenPosition = commandViewScreenPos, Flags = MouseFlags.LeftButtonPressed });
|
||||
app.InjectMouse (new Mouse { ScreenPosition = commandViewScreenPos, Flags = MouseFlags.LeftButtonReleased });
|
||||
|
||||
// Assert: The checkbox should be toggled when clicking on it
|
||||
Assert.Equal (1, checkBoxCheckedCount);
|
||||
|
||||
runnable.Dispose ();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that nested views (SuperView with MouseHighlightStates, SubView with MouseHighlightStates)
|
||||
/// route events to the deepest view under the mouse.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void MouseHighlightStates_Nested_Routes_To_Deepest_View ()
|
||||
{
|
||||
// Arrange
|
||||
VirtualTimeProvider time = new ();
|
||||
using IApplication app = Application.Create (time);
|
||||
app.Init (DriverRegistry.Names.ANSI);
|
||||
|
||||
var superViewActivateCount = 0;
|
||||
var subView1ActivateCount = 0;
|
||||
var subView2ActivateCount = 0;
|
||||
|
||||
Runnable runnable = new ();
|
||||
|
||||
View superView = new ()
|
||||
{
|
||||
Id = "superView",
|
||||
X = 0,
|
||||
Y = 0,
|
||||
Width = 20,
|
||||
Height = 20,
|
||||
MouseHighlightStates = MouseState.In
|
||||
};
|
||||
|
||||
superView.Activating += (_, _) => { superViewActivateCount++; };
|
||||
|
||||
View subView1 = new ()
|
||||
{
|
||||
Id = "subView1",
|
||||
X = 5,
|
||||
Y = 5,
|
||||
Width = 10,
|
||||
Height = 10,
|
||||
MouseHighlightStates = MouseState.In
|
||||
};
|
||||
|
||||
subView1.Activating += (_, _) => { subView1ActivateCount++; };
|
||||
|
||||
View subView2 = new ()
|
||||
{
|
||||
Id = "subView2",
|
||||
X = 2,
|
||||
Y = 2,
|
||||
Width = 5,
|
||||
Height = 5,
|
||||
CanFocus = true
|
||||
};
|
||||
|
||||
subView2.Activating += (_, _) => { subView2ActivateCount++; };
|
||||
|
||||
superView.Add (subView1);
|
||||
subView1.Add (subView2);
|
||||
runnable.Add (superView);
|
||||
app.Begin (runnable);
|
||||
|
||||
// Act: Click on subView2 (screen position: SuperView(0,0) + subView1(5,5) + subView2(2,2) = 7,7)
|
||||
app.InjectMouse (new Mouse { ScreenPosition = new Point (7, 7), Flags = MouseFlags.LeftButtonPressed });
|
||||
app.InjectMouse (new Mouse { ScreenPosition = new Point (7, 7), Flags = MouseFlags.LeftButtonReleased });
|
||||
|
||||
// Assert: Only the deepest view (subView2) should receive the event
|
||||
Assert.Equal (1, subView2ActivateCount);
|
||||
Assert.Equal (0, subView1ActivateCount);
|
||||
Assert.Equal (0, superViewActivateCount);
|
||||
|
||||
runnable.Dispose ();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user