Cleans up/Refactors View.Subviews (#3962)

* Subview clean up

* New Add/Remove event pattern

* Using Logging

* cleanup

* Subview -> SubView

* Test code cleanup. Killed many warnings.

* Fix tznind feedback

* Refactored AllViewTest helpers

* Moved keyboard tests to parallel

* Moved mouse tests to parallel

* Moved view tests to parallel

* Test code cleanup. Killed many warnings.

* dupe test

* Some mouse tests can't run in parallel because MouseGrabView

* Made SpinnerView more testable

* Moved more tests

* SubViews to IReadOnlyCollection<View>

* SubViews to IReadOnlyCollection<View> 2

* scrollbar tests

* shortcut tests

* Use InternalSubViews vs. _subviews

* Nuked View.IsAdded.
Added View.SuperViewChanged.

* API doc updats

* Unit Test tweak

* Unit Test tweak
This commit is contained in:
Tig
2025-03-08 15:42:17 -07:00
committed by GitHub
parent 85cf6619ed
commit acb5979e6c
226 changed files with 7235 additions and 7188 deletions

View File

@@ -1,4 +1,5 @@
using System.Collections.Concurrent;
#nullable enable
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
using Moq;
@@ -298,7 +299,7 @@ public class ApplicationV2Tests
v2.Shutdown ();
v2.Shutdown ();
outputMock.Verify(o=>o.Dispose (),Times.Once);
outputMock!.Verify(o=>o.Dispose (),Times.Once);
}
[Fact]
public void TestRepeatedInitCalls_WarnsAndIgnores ()
@@ -322,7 +323,7 @@ public class ApplicationV2Tests
It.IsAny<EventId> (),
It.Is<It.IsAnyType> ((v, t) => v.ToString () == "Init called multiple times without shutdown, ignoring."),
It.IsAny<Exception> (),
It.IsAny<Func<It.IsAnyType, Exception, string>> ())
It.IsAny<Func<It.IsAnyType, Exception, string>> ()!)
,Times.Exactly (2));
v2.Shutdown ();

View File

@@ -5,67 +5,74 @@ using ButtonState = Terminal.Gui.WindowsConsole.ButtonState;
using MouseEventRecord = Terminal.Gui.WindowsConsole.MouseEventRecord;
namespace UnitTests.ConsoleDrivers.V2;
public class WindowsInputProcessorTests
{
[Fact]
public void Test_ProcessQueue_CapitalHLowerE ()
{
var queue = new ConcurrentQueue<InputRecord> ();
ConcurrentQueue<InputRecord> queue = new ();
queue.Enqueue (new InputRecord()
{
EventType = WindowsConsole.EventType.Key,
KeyEvent = new WindowsConsole.KeyEventRecord ()
{
bKeyDown = true,
UnicodeChar = 'H',
dwControlKeyState = WindowsConsole.ControlKeyState.CapslockOn,
wVirtualKeyCode = (ConsoleKeyMapping.VK)72,
wVirtualScanCode = 35
}
});
queue.Enqueue (new InputRecord ()
{
EventType = WindowsConsole.EventType.Key,
KeyEvent = new WindowsConsole.KeyEventRecord ()
{
bKeyDown = false,
UnicodeChar = 'H',
dwControlKeyState = WindowsConsole.ControlKeyState.CapslockOn,
wVirtualKeyCode = (ConsoleKeyMapping.VK)72,
wVirtualScanCode = 35
}
});
queue.Enqueue (new InputRecord ()
{
EventType = WindowsConsole.EventType.Key,
KeyEvent = new WindowsConsole.KeyEventRecord ()
{
bKeyDown = true,
UnicodeChar = 'i',
dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
wVirtualKeyCode = (ConsoleKeyMapping.VK)73,
wVirtualScanCode = 23
}
});
queue.Enqueue (new InputRecord ()
{
EventType = WindowsConsole.EventType.Key,
KeyEvent = new WindowsConsole.KeyEventRecord ()
{
bKeyDown = false,
UnicodeChar = 'i',
dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
wVirtualKeyCode = (ConsoleKeyMapping.VK)73,
wVirtualScanCode = 23
}
});
queue.Enqueue (
new()
{
EventType = WindowsConsole.EventType.Key,
KeyEvent = new()
{
bKeyDown = true,
UnicodeChar = 'H',
dwControlKeyState = WindowsConsole.ControlKeyState.CapslockOn,
wVirtualKeyCode = (ConsoleKeyMapping.VK)72,
wVirtualScanCode = 35
}
});
queue.Enqueue (
new()
{
EventType = WindowsConsole.EventType.Key,
KeyEvent = new()
{
bKeyDown = false,
UnicodeChar = 'H',
dwControlKeyState = WindowsConsole.ControlKeyState.CapslockOn,
wVirtualKeyCode = (ConsoleKeyMapping.VK)72,
wVirtualScanCode = 35
}
});
queue.Enqueue (
new()
{
EventType = WindowsConsole.EventType.Key,
KeyEvent = new()
{
bKeyDown = true,
UnicodeChar = 'i',
dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
wVirtualKeyCode = (ConsoleKeyMapping.VK)73,
wVirtualScanCode = 23
}
});
queue.Enqueue (
new()
{
EventType = WindowsConsole.EventType.Key,
KeyEvent = new()
{
bKeyDown = false,
UnicodeChar = 'i',
dwControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
wVirtualKeyCode = (ConsoleKeyMapping.VK)73,
wVirtualScanCode = 23
}
});
var processor = new WindowsInputProcessor (queue);
List<Key> ups = new List<Key> ();
List<Key> downs = new List<Key> ();
List<Key> ups = new ();
List<Key> downs = new ();
processor.KeyUp += (s, e) => { ups.Add (e); };
processor.KeyDown += (s, e) => { downs.Add (e); };
@@ -81,27 +88,27 @@ public class WindowsInputProcessorTests
Assert.Equal (Key.I, downs [1]);
}
[Fact]
public void Test_ProcessQueue_Mouse_Move ()
{
var queue = new ConcurrentQueue<InputRecord> ();
ConcurrentQueue<InputRecord> queue = new ();
queue.Enqueue (new InputRecord ()
{
EventType = WindowsConsole.EventType.Mouse,
MouseEvent = new WindowsConsole.MouseEventRecord
{
MousePosition = new WindowsConsole.Coord(32,31),
ButtonState = WindowsConsole.ButtonState.NoButtonPressed,
ControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
EventFlags = WindowsConsole.EventFlags.MouseMoved
}
});
queue.Enqueue (
new()
{
EventType = WindowsConsole.EventType.Mouse,
MouseEvent = new()
{
MousePosition = new (32, 31),
ButtonState = ButtonState.NoButtonPressed,
ControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
EventFlags = WindowsConsole.EventFlags.MouseMoved
}
});
var processor = new WindowsInputProcessor (queue);
List<MouseEventArgs> mouseEvents = new List<MouseEventArgs> ();
List<MouseEventArgs> mouseEvents = new ();
processor.MouseEvent += (s, e) => { mouseEvents.Add (e); };
@@ -109,35 +116,36 @@ public class WindowsInputProcessorTests
processor.ProcessQueue ();
var s = Assert.Single (mouseEvents);
Assert.Equal (s.Flags,MouseFlags.ReportMousePosition);
Assert.Equal (s.ScreenPosition,new Point (32,31));
MouseEventArgs s = Assert.Single (mouseEvents);
Assert.Equal (MouseFlags.ReportMousePosition, s.Flags);
Assert.Equal (s.ScreenPosition, new (32, 31));
}
[Theory]
[InlineData(WindowsConsole.ButtonState.Button1Pressed,MouseFlags.Button1Pressed)]
[InlineData (WindowsConsole.ButtonState.Button2Pressed, MouseFlags.Button2Pressed)]
[InlineData (WindowsConsole.ButtonState.Button3Pressed, MouseFlags.Button3Pressed)]
[InlineData (WindowsConsole.ButtonState.Button4Pressed, MouseFlags.Button4Pressed)]
internal void Test_ProcessQueue_Mouse_Pressed (WindowsConsole.ButtonState state,MouseFlags expectedFlag )
[InlineData (ButtonState.Button1Pressed, MouseFlags.Button1Pressed)]
[InlineData (ButtonState.Button2Pressed, MouseFlags.Button2Pressed)]
[InlineData (ButtonState.Button3Pressed, MouseFlags.Button3Pressed)]
[InlineData (ButtonState.Button4Pressed, MouseFlags.Button4Pressed)]
internal void Test_ProcessQueue_Mouse_Pressed (ButtonState state, MouseFlags expectedFlag)
{
var queue = new ConcurrentQueue<InputRecord> ();
ConcurrentQueue<InputRecord> queue = new ();
queue.Enqueue (new InputRecord ()
{
EventType = WindowsConsole.EventType.Mouse,
MouseEvent = new WindowsConsole.MouseEventRecord
{
MousePosition = new WindowsConsole.Coord (32, 31),
ButtonState = state,
ControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
EventFlags = WindowsConsole.EventFlags.MouseMoved
}
});
queue.Enqueue (
new()
{
EventType = WindowsConsole.EventType.Mouse,
MouseEvent = new()
{
MousePosition = new (32, 31),
ButtonState = state,
ControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
EventFlags = WindowsConsole.EventFlags.MouseMoved
}
});
var processor = new WindowsInputProcessor (queue);
List<MouseEventArgs> mouseEvents = new List<MouseEventArgs> ();
List<MouseEventArgs> mouseEvents = new ();
processor.MouseEvent += (s, e) => { mouseEvents.Add (e); };
@@ -145,34 +153,34 @@ public class WindowsInputProcessorTests
processor.ProcessQueue ();
var s = Assert.Single (mouseEvents);
MouseEventArgs s = Assert.Single (mouseEvents);
Assert.Equal (s.Flags, MouseFlags.ReportMousePosition | expectedFlag);
Assert.Equal (s.ScreenPosition, new Point (32, 31));
Assert.Equal (s.ScreenPosition, new (32, 31));
}
[Theory]
[InlineData (100, MouseFlags.WheeledUp)]
[InlineData ( -100, MouseFlags.WheeledDown)]
[InlineData (-100, MouseFlags.WheeledDown)]
internal void Test_ProcessQueue_Mouse_Wheel (int wheelValue, MouseFlags expectedFlag)
{
var queue = new ConcurrentQueue<InputRecord> ();
ConcurrentQueue<InputRecord> queue = new ();
queue.Enqueue (new InputRecord ()
{
EventType = WindowsConsole.EventType.Mouse,
MouseEvent = new WindowsConsole.MouseEventRecord
{
MousePosition = new WindowsConsole.Coord (32, 31),
ButtonState = (WindowsConsole.ButtonState)wheelValue,
ControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
EventFlags = WindowsConsole.EventFlags.MouseWheeled
}
});
queue.Enqueue (
new()
{
EventType = WindowsConsole.EventType.Mouse,
MouseEvent = new()
{
MousePosition = new (32, 31),
ButtonState = (ButtonState)wheelValue,
ControlKeyState = WindowsConsole.ControlKeyState.NoControlKeyPressed,
EventFlags = WindowsConsole.EventFlags.MouseWheeled
}
});
var processor = new WindowsInputProcessor (queue);
List<MouseEventArgs> mouseEvents = new List<MouseEventArgs> ();
List<MouseEventArgs> mouseEvents = new ();
processor.MouseEvent += (s, e) => { mouseEvents.Add (e); };
@@ -180,126 +188,133 @@ public class WindowsInputProcessorTests
processor.ProcessQueue ();
var s = Assert.Single (mouseEvents);
Assert.Equal (s.Flags,expectedFlag);
Assert.Equal (s.ScreenPosition, new Point (32, 31));
MouseEventArgs s = Assert.Single (mouseEvents);
Assert.Equal (s.Flags, expectedFlag);
Assert.Equal (s.ScreenPosition, new (32, 31));
}
public static IEnumerable<object []> MouseFlagTestData ()
{
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
new []
{
Tuple.Create(ButtonState.Button1Pressed, MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button1Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
Tuple.Create (ButtonState.Button1Pressed, MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button1Released | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
}
};
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
new []
{
Tuple.Create ( ButtonState.Button2Pressed, MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button2Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
}
};
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
{
Tuple.Create(ButtonState.Button3Pressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button3Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
Tuple.Create (ButtonState.Button2Pressed, MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button2Released | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
}
};
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
new []
{
Tuple.Create(ButtonState.Button4Pressed, MouseFlags.Button4Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button4Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
Tuple.Create (ButtonState.Button3Pressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button3Released | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
}
};
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
new []
{
Tuple.Create(ButtonState.RightmostButtonPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button3Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
Tuple.Create (ButtonState.Button4Pressed, MouseFlags.Button4Pressed | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button4Released | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
}
};
yield return new object []
{
new []
{
Tuple.Create (ButtonState.RightmostButtonPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button3Released | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition | MouseFlags.ReportMousePosition)
}
};
// Tests for holding down 2 buttons at once and releasing them one after the other
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
new []
{
Tuple.Create(ButtonState.Button1Pressed | ButtonState.Button2Pressed, MouseFlags.Button1Pressed | MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.Button1Pressed, MouseFlags.Button1Pressed | MouseFlags.Button2Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button1Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
Tuple.Create (
ButtonState.Button1Pressed | ButtonState.Button2Pressed,
MouseFlags.Button1Pressed | MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.Button1Pressed, MouseFlags.Button1Pressed | MouseFlags.Button2Released | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button1Released | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
}
};
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
new []
{
Tuple.Create(ButtonState.Button3Pressed | ButtonState.Button4Pressed, MouseFlags.Button3Pressed | MouseFlags.Button4Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.Button3Pressed, MouseFlags.Button3Pressed | MouseFlags.Button4Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button3Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
Tuple.Create (
ButtonState.Button3Pressed | ButtonState.Button4Pressed,
MouseFlags.Button3Pressed | MouseFlags.Button4Pressed | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.Button3Pressed, MouseFlags.Button3Pressed | MouseFlags.Button4Released | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button3Released | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
}
};
// Test for holding down 2 buttons at once and releasing them simultaneously
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
new []
{
Tuple.Create(ButtonState.Button1Pressed | ButtonState.Button2Pressed, MouseFlags.Button1Pressed | MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button1Released | MouseFlags.Button2Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
Tuple.Create (
ButtonState.Button1Pressed | ButtonState.Button2Pressed,
MouseFlags.Button1Pressed | MouseFlags.Button2Pressed | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button1Released | MouseFlags.Button2Released | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
}
};
// Test that rightmost and button 3 are the same button so 2 states is still only 1 flag
yield return new object []
{
new Tuple<ButtonState, MouseFlags>[]
new []
{
Tuple.Create(ButtonState.Button3Pressed | ButtonState.RightmostButtonPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.Button3Pressed | ButtonState.RightmostButtonPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
// Can swap between without raising the released
Tuple.Create(ButtonState.Button3Pressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.RightmostButtonPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.Button3Pressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.RightmostButtonPressed, MouseFlags.Button3Pressed | MouseFlags.ReportMousePosition),
// Now with neither we get released
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.Button3Released | MouseFlags.ReportMousePosition),
Tuple.Create(ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.Button3Released | MouseFlags.ReportMousePosition),
Tuple.Create (ButtonState.NoButtonPressed, MouseFlags.ReportMousePosition)
}
};
}
[Theory]
[MemberData (nameof (MouseFlagTestData))]
internal void MouseFlags_Should_Map_Correctly (Tuple<ButtonState, MouseFlags>[] inputOutputPairs)
internal void MouseFlags_Should_Map_Correctly (Tuple<ButtonState, MouseFlags> [] inputOutputPairs)
{
var processor = new WindowsInputProcessor (new ());
foreach (var pair in inputOutputPairs)
foreach (Tuple<ButtonState, MouseFlags> pair in inputOutputPairs)
{
var mockEvent = new MouseEventRecord { ButtonState = pair.Item1 };
var result = processor.ToDriverMouse (mockEvent);
MouseEventArgs result = processor.ToDriverMouse (mockEvent);
Assert.Equal (pair.Item2, result.Flags);
}
}
}