123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753 |
- using Xunit.Abstractions;
- namespace Terminal.Gui.ApplicationTests;
- /// <summary>
- /// Application tests for keyboard support.
- /// </summary>
- public class KeyboardTests
- {
- public KeyboardTests (ITestOutputHelper output)
- {
- _output = output;
- #if DEBUG_IDISPOSABLE
- View.Instances.Clear ();
- RunState.Instances.Clear ();
- #endif
- }
- private readonly ITestOutputHelper _output;
- private object _timeoutLock;
- [Fact (Skip = "No longer valid test.")]
- [AutoInitShutdown]
- public void EnsuresTopOnFront_CanFocus_False_By_Keyboard ()
- {
- Toplevel top = new ();
- var win = new Window
- {
- Title = "win",
- X = 0,
- Y = 0,
- Width = 20,
- Height = 10
- };
- var tf = new TextField { Width = 10 };
- win.Add (tf);
- var win2 = new Window
- {
- Title = "win2",
- X = 22,
- Y = 0,
- Width = 20,
- Height = 10
- };
- var tf2 = new TextField { Width = 10 };
- win2.Add (tf2);
- top.Add (win, win2);
- Application.Begin (top);
- Assert.True (win.CanFocus);
- Assert.True (win.HasFocus);
- Assert.True (win2.CanFocus);
- Assert.False (win2.HasFocus);
- Assert.Equal ("win", ((Window)top.Subviews [^1]).Title);
- win.CanFocus = false;
- Assert.False (win.CanFocus);
- Assert.False (win.HasFocus);
- Assert.True (win2.CanFocus);
- Assert.True (win2.HasFocus);
- Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
- Application.RaiseKeyDownEvent (Key.F6);
- Assert.True (win2.CanFocus);
- Assert.False (win.HasFocus);
- Assert.True (win2.CanFocus);
- Assert.True (win2.HasFocus);
- Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
- Application.RaiseKeyDownEvent (Key.F6);
- Assert.False (win.CanFocus);
- Assert.False (win.HasFocus);
- Assert.True (win2.CanFocus);
- Assert.True (win2.HasFocus);
- Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
- top.Dispose ();
- }
- [Fact (Skip = "No longer valid test.")]
- [AutoInitShutdown]
- public void EnsuresTopOnFront_CanFocus_True_By_Keyboard ()
- {
- Toplevel top = new ();
- var win = new Window
- {
- Title = "win",
- X = 0,
- Y = 0,
- Width = 20,
- Height = 10
- };
- var tf = new TextField { Width = 10 };
- win.Add (tf);
- var win2 = new Window
- {
- Title = "win2",
- X = 22,
- Y = 0,
- Width = 20,
- Height = 10
- };
- var tf2 = new TextField { Width = 10 };
- win2.Add (tf2);
- top.Add (win, win2);
- Application.Begin (top);
- Assert.True (win.CanFocus);
- Assert.True (win.HasFocus);
- Assert.True (win2.CanFocus);
- Assert.False (win2.HasFocus);
- Assert.Equal ("win", ((Window)top.Subviews [^1]).Title);
- Application.RaiseKeyDownEvent (Key.F6);
- Assert.True (win.CanFocus);
- Assert.False (win.HasFocus);
- Assert.True (win2.CanFocus);
- Assert.True (win2.HasFocus);
- Assert.Equal ("win2", ((Window)top.Subviews [^1]).Title);
- Application.RaiseKeyDownEvent (Key.F6);
- Assert.True (win.CanFocus);
- Assert.True (win.HasFocus);
- Assert.True (win2.CanFocus);
- Assert.False (win2.HasFocus);
- Assert.Equal ("win", ((Window)top.Subviews [^1]).Title);
- top.Dispose ();
- }
- [Fact]
- [AutoInitShutdown]
- public void KeyBinding_Application_KeyBindings_Add_Adds ()
- {
- Application.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Accept);
- Application.KeyBindings.Add (Key.B, KeyBindingScope.Application, Command.Accept);
- Assert.True (Application.KeyBindings.TryGet (Key.A, out KeyBinding binding));
- Assert.Null (binding.BoundView);
- Assert.True (Application.KeyBindings.TryGet (Key.B, out binding));
- Assert.Null (binding.BoundView);
- }
- [Fact]
- [AutoInitShutdown]
- public void KeyBinding_Application_RemoveKeyBinding_Removes ()
- {
- Application.KeyBindings.Add (Key.A, KeyBindingScope.Application, Command.Accept);
- Assert.True (Application.KeyBindings.TryGet (Key.A, out _));
- Application.KeyBindings.Remove (Key.A);
- Assert.False (Application.KeyBindings.TryGet (Key.A, out _));
- }
- [Fact]
- public void KeyBinding_OnKeyDown ()
- {
- Application.Top = new Toplevel ();
- var view = new ScopedKeyBindingView ();
- var keyWasHandled = false;
- view.KeyDownNotHandled += (s, e) => keyWasHandled = true;
- Application.Top.Add (view);
- Application.RaiseKeyDownEvent (Key.A);
- Assert.False (keyWasHandled);
- Assert.True (view.ApplicationCommand);
- keyWasHandled = false;
- view.ApplicationCommand = false;
- Application.KeyBindings.Remove (KeyCode.A);
- Application.RaiseKeyDownEvent (Key.A); // old
- Assert.False (keyWasHandled);
- Assert.False (view.ApplicationCommand);
- Application.KeyBindings.Add (Key.A.WithCtrl, view, Command.Save);
- Application.RaiseKeyDownEvent (Key.A); // old
- Assert.False (keyWasHandled);
- Assert.False (view.ApplicationCommand);
- Application.RaiseKeyDownEvent (Key.A.WithCtrl); // new
- Assert.False (keyWasHandled);
- Assert.True (view.ApplicationCommand);
- keyWasHandled = false;
- Application.RaiseKeyDownEvent (Key.H);
- Assert.False (keyWasHandled);
- keyWasHandled = false;
- Assert.False (view.HasFocus);
- Application.RaiseKeyDownEvent (Key.F);
- Assert.False (keyWasHandled);
- Assert.True (view.ApplicationCommand);
- Assert.True (view.HotKeyCommand);
- Assert.False (view.FocusedCommand);
- Application.Top.Dispose ();
- Application.ResetState (true);
- }
- [Fact]
- [AutoInitShutdown]
- public void KeyBinding_OnKeyDown_Negative ()
- {
- var view = new ScopedKeyBindingView ();
- var keyWasHandled = false;
- view.KeyDownNotHandled += (s, e) => keyWasHandled = true;
- var top = new Toplevel ();
- top.Add (view);
- Application.Begin (top);
- Application.RaiseKeyDownEvent (Key.A.WithCtrl);
- Assert.False (keyWasHandled);
- Assert.False (view.ApplicationCommand);
- Assert.False (view.HotKeyCommand);
- Assert.False (view.FocusedCommand);
- keyWasHandled = false;
- Assert.False (view.HasFocus);
- Application.RaiseKeyDownEvent (Key.Z);
- Assert.False (keyWasHandled);
- Assert.False (view.ApplicationCommand);
- Assert.False (view.HotKeyCommand);
- Assert.False (view.FocusedCommand);
- top.Dispose ();
- }
- [Fact]
- [AutoInitShutdown]
- public void KeyBinding_View_KeyBindings_Add_Adds ()
- {
- View view1 = new ();
- Application.KeyBindings.Add (Key.A, view1, Command.Accept);
- View view2 = new ();
- Application.KeyBindings.Add (Key.B, view2, Command.Accept);
- Assert.True (Application.KeyBindings.TryGet (Key.A, out KeyBinding binding));
- Assert.Equal (view1, binding.BoundView);
- Assert.True (Application.KeyBindings.TryGet (Key.B, out binding));
- Assert.Equal (view2, binding.BoundView);
- }
- [Fact]
- [AutoInitShutdown]
- public void KeyBinding_View_KeyBindings_RemoveKeyBinding_Removes ()
- {
- View view1 = new ();
- Application.KeyBindings.Add (Key.A, view1, Command.Accept);
- View view2 = new ();
- Application.KeyBindings.Add (Key.B, view1, Command.Accept);
- Application.KeyBindings.Remove (Key.A, view1);
- Assert.False (Application.KeyBindings.TryGet (Key.A, out _));
- }
- [Fact]
- public void KeyUp_Event ()
- {
- Application.Init (new FakeDriver ());
- // Setup some fake keypresses (This)
- var input = "Tests";
- Key originalQuitKey = Application.QuitKey;
- Application.QuitKey = Key.Q.WithCtrl;
- // Put a control-q in at the end
- FakeConsole.MockKeyPresses.Push (new ('Q', ConsoleKey.Q, false, false, true));
- foreach (char c in input.Reverse ())
- {
- if (char.IsLetter (c))
- {
- FakeConsole.MockKeyPresses.Push (
- new (
- c,
- (ConsoleKey)char.ToUpper (c),
- char.IsUpper (c),
- false,
- false
- )
- );
- }
- else
- {
- FakeConsole.MockKeyPresses.Push (
- new (
- c,
- (ConsoleKey)c,
- false,
- false,
- false
- )
- );
- }
- }
- int stackSize = FakeConsole.MockKeyPresses.Count;
- var iterations = 0;
- Application.Iteration += (s, a) =>
- {
- iterations++;
- // Stop if we run out of control...
- if (iterations > 10)
- {
- Application.RequestStop ();
- }
- };
- var keyUps = 0;
- var output = string.Empty;
- var top = new Toplevel ();
- top.KeyUp += (sender, args) =>
- {
- if (args.KeyCode != (KeyCode.CtrlMask | KeyCode.Q))
- {
- output += args.AsRune;
- }
- keyUps++;
- };
- Application.Run (top);
- Application.QuitKey = originalQuitKey;
- // Input string should match output
- Assert.Equal (input, output);
- // # of key up events should match stack size
- //Assert.Equal (stackSize, keyUps);
- // We can't use numbers variables on the left side of an Assert.Equal/NotEqual,
- // it must be literal (Linux only).
- Assert.Equal (6, keyUps);
- // # of key up events should match # of iterations
- Assert.Equal (stackSize, iterations);
- top.Dispose ();
- Application.Shutdown ();
- Assert.Null (Application.Top);
- Assert.Null (Application.MainLoop);
- Assert.Null (Application.Driver);
- }
- [Fact]
- public void NextTabGroupKey_Moves_Focus_To_TabStop_In_Next_TabGroup ()
- {
- // Arrange
- Application.Navigation = new ();
- var top = new Toplevel ();
- var view1 = new View
- {
- Id = "view1",
- CanFocus = true,
- TabStop = TabBehavior.TabGroup
- };
- var subView1 = new View
- {
- Id = "subView1",
- CanFocus = true,
- TabStop = TabBehavior.TabStop
- };
- view1.Add (subView1);
- var view2 = new View
- {
- Id = "view2",
- CanFocus = true,
- TabStop = TabBehavior.TabGroup
- };
- var subView2 = new View
- {
- Id = "subView2",
- CanFocus = true,
- TabStop = TabBehavior.TabStop
- };
- view2.Add (subView2);
- top.Add (view1, view2);
- Application.Top = top;
- view1.SetFocus ();
- Assert.True (view1.HasFocus);
- Assert.True (subView1.HasFocus);
- // Act
- Application.RaiseKeyDownEvent (Application.NextTabGroupKey);
- // Assert
- Assert.True (view2.HasFocus);
- Assert.True (subView2.HasFocus);
- top.Dispose ();
- Application.Navigation = null;
- }
- [Fact]
- public void NextTabGroupKey_PrevTabGroupKey_Tests ()
- {
- Application.Init (new FakeDriver ());
- Toplevel top = new (); // TabGroup
- var w1 = new Window (); // TabGroup
- var v1 = new TextField (); // TabStop
- var v2 = new TextView (); // TabStop
- w1.Add (v1, v2);
- var w2 = new Window (); // TabGroup
- var v3 = new CheckBox (); // TabStop
- var v4 = new Button (); // TabStop
- w2.Add (v3, v4);
- top.Add (w1, w2);
- Application.Iteration += (s, a) =>
- {
- Assert.True (v1.HasFocus);
- // Across TabGroups
- Application.RaiseKeyDownEvent (Key.F6);
- Assert.True (v3.HasFocus);
- Application.RaiseKeyDownEvent (Key.F6);
- Assert.True (v1.HasFocus);
- Application.RaiseKeyDownEvent (Key.F6.WithShift);
- Assert.True (v3.HasFocus);
- Application.RaiseKeyDownEvent (Key.F6.WithShift);
- Assert.True (v1.HasFocus);
- // Restore?
- Application.RaiseKeyDownEvent (Key.Tab);
- Assert.True (v2.HasFocus);
- Application.RaiseKeyDownEvent (Key.F6);
- Assert.True (v3.HasFocus);
- Application.RaiseKeyDownEvent (Key.F6);
- Assert.True (v1.HasFocus);
- Application.RequestStop ();
- };
- Application.Run (top);
- // Replacing the defaults keys to avoid errors on others unit tests that are using it.
- Application.NextTabGroupKey = Key.PageDown.WithCtrl;
- Application.PrevTabGroupKey = Key.PageUp.WithCtrl;
- Application.QuitKey = Key.Q.WithCtrl;
- Assert.Equal (KeyCode.PageDown | KeyCode.CtrlMask, Application.NextTabGroupKey.KeyCode);
- Assert.Equal (KeyCode.PageUp | KeyCode.CtrlMask, Application.PrevTabGroupKey.KeyCode);
- Assert.Equal (KeyCode.Q | KeyCode.CtrlMask, Application.QuitKey.KeyCode);
- top.Dispose ();
- // Shutdown must be called to safely clean up Application if Init has been called
- Application.Shutdown ();
- }
- [Fact]
- public void NextTabKey_Moves_Focus_To_Next_TabStop ()
- {
- // Arrange
- Application.Navigation = new ();
- var top = new Toplevel ();
- var view1 = new View { Id = "view1", CanFocus = true };
- var view2 = new View { Id = "view2", CanFocus = true };
- top.Add (view1, view2);
- Application.Top = top;
- view1.SetFocus ();
- // Act
- Application.RaiseKeyDownEvent (Application.NextTabKey);
- // Assert
- Assert.True (view2.HasFocus);
- top.Dispose ();
- Application.Navigation = null;
- }
- [Fact]
- public void PrevTabGroupKey_Moves_Focus_To_TabStop_In_Prev_TabGroup ()
- {
- // Arrange
- Application.Navigation = new ();
- var top = new Toplevel ();
- var view1 = new View
- {
- Id = "view1",
- CanFocus = true,
- TabStop = TabBehavior.TabGroup
- };
- var subView1 = new View
- {
- Id = "subView1",
- CanFocus = true,
- TabStop = TabBehavior.TabStop
- };
- view1.Add (subView1);
- var view2 = new View
- {
- Id = "view2",
- CanFocus = true,
- TabStop = TabBehavior.TabGroup
- };
- var subView2 = new View
- {
- Id = "subView2",
- CanFocus = true,
- TabStop = TabBehavior.TabStop
- };
- view2.Add (subView2);
- top.Add (view1, view2);
- Application.Top = top;
- view1.SetFocus ();
- Assert.True (view1.HasFocus);
- Assert.True (subView1.HasFocus);
- // Act
- Application.RaiseKeyDownEvent (Application.PrevTabGroupKey);
- // Assert
- Assert.True (view2.HasFocus);
- Assert.True (subView2.HasFocus);
- top.Dispose ();
- Application.Navigation = null;
- }
- [Fact]
- public void PrevTabKey_Moves_Focus_To_Prev_TabStop ()
- {
- // Arrange
- Application.Navigation = new ();
- var top = new Toplevel ();
- var view1 = new View { Id = "view1", CanFocus = true };
- var view2 = new View { Id = "view2", CanFocus = true };
- top.Add (view1, view2);
- Application.Top = top;
- view1.SetFocus ();
- // Act
- Application.RaiseKeyDownEvent (Application.NextTabKey);
- // Assert
- Assert.True (view2.HasFocus);
- top.Dispose ();
- Application.Navigation = null;
- }
- [Fact]
- public void QuitKey_Default_Is_Esc ()
- {
- Application.ResetState (true);
- // Before Init
- Assert.Equal (Key.Esc, Application.QuitKey);
- Application.Init (new FakeDriver ());
- // After Init
- Assert.Equal (Key.Esc, Application.QuitKey);
- Application.Shutdown ();
- }
- [Fact]
- [AutoInitShutdown]
- public void QuitKey_Getter_Setter ()
- {
- Toplevel top = new ();
- var isQuiting = false;
- top.Closing += (s, e) =>
- {
- isQuiting = true;
- e.Cancel = true;
- };
- Application.Begin (top);
- top.Running = true;
- Key prevKey = Application.QuitKey;
- Application.RaiseKeyDownEvent (Application.QuitKey);
- Assert.True (isQuiting);
- isQuiting = false;
- Application.RaiseKeyDownEvent (Application.QuitKey);
- Assert.True (isQuiting);
- isQuiting = false;
- Application.QuitKey = Key.C.WithCtrl;
- Application.RaiseKeyDownEvent (prevKey); // Should not quit
- Assert.False (isQuiting);
- Application.RaiseKeyDownEvent (Key.Q.WithCtrl); // Should not quit
- Assert.False (isQuiting);
- Application.RaiseKeyDownEvent (Application.QuitKey);
- Assert.True (isQuiting);
- // Reset the QuitKey to avoid throws errors on another tests
- Application.QuitKey = prevKey;
- top.Dispose ();
- }
- [Fact]
- public void QuitKey_Quits ()
- {
- Assert.Null (_timeoutLock);
- _timeoutLock = new ();
- uint abortTime = 500;
- var initialized = false;
- var iteration = 0;
- var shutdown = false;
- object timeout = null;
- Application.InitializedChanged += OnApplicationOnInitializedChanged;
- Application.Init (new FakeDriver ());
- Assert.True (initialized);
- Assert.False (shutdown);
- _output.WriteLine ("Application.Run<Toplevel> ().Dispose ()..");
- Application.Run<Toplevel> ().Dispose ();
- _output.WriteLine ("Back from Application.Run<Toplevel> ().Dispose ()");
- Assert.True (initialized);
- Assert.False (shutdown);
- Assert.Equal (1, iteration);
- Application.Shutdown ();
- Application.InitializedChanged -= OnApplicationOnInitializedChanged;
- lock (_timeoutLock)
- {
- if (timeout is { })
- {
- Application.RemoveTimeout (timeout);
- timeout = null;
- }
- }
- Assert.True (initialized);
- Assert.True (shutdown);
- #if DEBUG_IDISPOSABLE
- Assert.Empty (View.Instances);
- #endif
- lock (_timeoutLock)
- {
- _timeoutLock = null;
- }
- return;
- void OnApplicationOnInitializedChanged (object s, EventArgs<bool> a)
- {
- _output.WriteLine ("OnApplicationOnInitializedChanged: {0}", a.CurrentValue);
- if (a.CurrentValue)
- {
- Application.Iteration += OnApplicationOnIteration;
- initialized = true;
- lock (_timeoutLock)
- {
- timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (abortTime), ForceCloseCallback);
- }
- }
- else
- {
- Application.Iteration -= OnApplicationOnIteration;
- shutdown = true;
- }
- }
- bool ForceCloseCallback ()
- {
- lock (_timeoutLock)
- {
- _output.WriteLine ($"ForceCloseCallback. iteration: {iteration}");
- if (timeout is { })
- {
- timeout = null;
- }
- }
- Application.ResetState (true);
- Assert.Fail ($"Failed to Quit with {Application.QuitKey} after {abortTime}ms. Force quit.");
- return false;
- }
- void OnApplicationOnIteration (object s, IterationEventArgs a)
- {
- _output.WriteLine ("Iteration: {0}", iteration);
- iteration++;
- Assert.True (iteration < 2, "Too many iterations, something is wrong.");
- if (Application.Initialized)
- {
- _output.WriteLine (" Pressing QuitKey");
- Application.RaiseKeyDownEvent (Application.QuitKey);
- }
- }
- }
- // Test View for testing Application key Bindings
- public class ScopedKeyBindingView : View
- {
- public ScopedKeyBindingView ()
- {
- AddCommand (Command.Save, () => ApplicationCommand = true);
- AddCommand (Command.HotKey, () => HotKeyCommand = true);
- AddCommand (Command.Left, () => FocusedCommand = true);
- Application.KeyBindings.Add (Key.A, this, Command.Save);
- HotKey = KeyCode.H;
- KeyBindings.Add (Key.F, Command.Left);
- }
- public bool ApplicationCommand { get; set; }
- public bool FocusedCommand { get; set; }
- public bool HotKeyCommand { get; set; }
- }
- }
|