| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634 |
- #nullable enable
- using System.Collections.Concurrent;
- using System.Runtime.CompilerServices;
- using Microsoft.Extensions.Logging;
- using Moq;
- using TerminalGuiFluentTesting;
- namespace UnitTests.ApplicationTests;
- public class ApplicationImplTests
- {
- public ApplicationImplTests ()
- {
- ConsoleDriver.RunningUnitTests = true;
- }
- private ApplicationImpl NewApplicationImpl (TestDriver driver = TestDriver.DotNet)
- {
- if (driver == TestDriver.DotNet)
- {
- var netInput = new Mock<INetInput> ();
- SetupRunInputMockMethodToBlock (netInput);
- var m = new Mock<IComponentFactory<ConsoleKeyInfo>> ();
- m.Setup (f => f.CreateInput ()).Returns (netInput.Object);
- m.Setup (f => f.CreateInputProcessor (It.IsAny<ConcurrentQueue<ConsoleKeyInfo>> ())).Returns (Mock.Of <IInputProcessor> ());
- m.Setup (f => f.CreateOutput ()).Returns (Mock.Of<IConsoleOutput> ());
- m.Setup (f => f.CreateConsoleSizeMonitor (It.IsAny<IConsoleOutput> (),It.IsAny<IOutputBuffer> ())).Returns (Mock.Of<IConsoleSizeMonitor> ());
- return new (m.Object);
- }
- else
- {
- var winInput = new Mock<IConsoleInput<WindowsConsole.InputRecord>> ();
- SetupRunInputMockMethodToBlock (winInput);
- var m = new Mock<IComponentFactory<WindowsConsole.InputRecord>> ();
- m.Setup (f => f.CreateInput ()).Returns (winInput.Object);
- m.Setup (f => f.CreateInputProcessor (It.IsAny<ConcurrentQueue<WindowsConsole.InputRecord>> ())).Returns (Mock.Of<IInputProcessor> ());
- m.Setup (f => f.CreateOutput ()).Returns (Mock.Of<IConsoleOutput> ());
- m.Setup (f => f.CreateConsoleSizeMonitor (It.IsAny<IConsoleOutput> (), It.IsAny<IOutputBuffer> ())).Returns (Mock.Of<IConsoleSizeMonitor> ());
- return new (m.Object);
- }
- }
- [Fact]
- public void Init_CreatesKeybindings ()
- {
- var orig = ApplicationImpl.Instance;
- var v2 = NewApplicationImpl ();
- ApplicationImpl.ChangeInstance (v2);
- Application.KeyBindings.Clear ();
- Assert.Empty (Application.KeyBindings.GetBindings ());
- v2.Init ();
- Assert.NotEmpty (Application.KeyBindings.GetBindings ());
- v2.Shutdown ();
- ApplicationImpl.ChangeInstance (orig);
- }
- [Fact]
- public void Init_DriverIsFacade ()
- {
- var orig = ApplicationImpl.Instance;
- var v2 = NewApplicationImpl ();
- ApplicationImpl.ChangeInstance (v2);
- Assert.Null (Application.Driver);
- v2.Init ();
- Assert.NotNull (Application.Driver);
- var type = Application.Driver.GetType ();
- Assert.True (type.IsGenericType);
- Assert.True (type.GetGenericTypeDefinition () == typeof (ConsoleDriverFacade<>));
- v2.Shutdown ();
- Assert.Null (Application.Driver);
- ApplicationImpl.ChangeInstance (orig);
- }
- /*
- [Fact]
- public void Init_ExplicitlyRequestWin ()
- {
- var orig = ApplicationImpl.Instance;
- Assert.Null (Application.Driver);
- var netInput = new Mock<INetInput> (MockBehavior.Strict);
- var netOutput = new Mock<IConsoleOutput> (MockBehavior.Strict);
- var winInput = new Mock<IWindowsInput> (MockBehavior.Strict);
- var winOutput = new Mock<IConsoleOutput> (MockBehavior.Strict);
- winInput.Setup (i => i.Initialize (It.IsAny<ConcurrentQueue<WindowsConsole.InputRecord>> ()))
- .Verifiable (Times.Once);
- SetupRunInputMockMethodToBlock (winInput);
- winInput.Setup (i => i.Dispose ())
- .Verifiable (Times.Once);
- winOutput.Setup (i => i.Dispose ())
- .Verifiable (Times.Once);
- var v2 = new ApplicationV2 (
- () => netInput.Object,
- () => netOutput.Object,
- () => winInput.Object,
- () => winOutput.Object);
- ApplicationImpl.ChangeInstance (v2);
- Assert.Null (Application.Driver);
- v2.Init (null, "v2win");
- Assert.NotNull (Application.Driver);
- var type = Application.Driver.GetType ();
- Assert.True (type.IsGenericType);
- Assert.True (type.GetGenericTypeDefinition () == typeof (ConsoleDriverFacade<>));
- v2.Shutdown ();
- Assert.Null (Application.Driver);
- winInput.VerifyAll ();
- ApplicationImpl.ChangeInstance (orig);
- }
- [Fact]
- public void Init_ExplicitlyRequestNet ()
- {
- var orig = ApplicationImpl.Instance;
- var netInput = new Mock<INetInput> (MockBehavior.Strict);
- var netOutput = new Mock<IConsoleOutput> (MockBehavior.Strict);
- var winInput = new Mock<IWindowsInput> (MockBehavior.Strict);
- var winOutput = new Mock<IConsoleOutput> (MockBehavior.Strict);
- netInput.Setup (i => i.Initialize (It.IsAny<ConcurrentQueue<ConsoleKeyInfo>> ()))
- .Verifiable (Times.Once);
- SetupRunInputMockMethodToBlock (netInput);
- netInput.Setup (i => i.Dispose ())
- .Verifiable (Times.Once);
- netOutput.Setup (i => i.Dispose ())
- .Verifiable (Times.Once);
- var v2 = new ApplicationV2 (
- () => netInput.Object,
- () => netOutput.Object,
- () => winInput.Object,
- () => winOutput.Object);
- ApplicationImpl.ChangeInstance (v2);
- Assert.Null (Application.Driver);
- v2.Init (null, "v2net");
- Assert.NotNull (Application.Driver);
- var type = Application.Driver.GetType ();
- Assert.True (type.IsGenericType);
- Assert.True (type.GetGenericTypeDefinition () == typeof (ConsoleDriverFacade<>));
- v2.Shutdown ();
- Assert.Null (Application.Driver);
- netInput.VerifyAll ();
- ApplicationImpl.ChangeInstance (orig);
- }
- */
- private void SetupRunInputMockMethodToBlock (Mock<IConsoleInput<WindowsConsole.InputRecord>> winInput)
- {
- winInput.Setup (r => r.Run (It.IsAny<CancellationToken> ()))
- .Callback<CancellationToken> (token =>
- {
- // Simulate an infinite loop that checks for cancellation
- while (!token.IsCancellationRequested)
- {
- // Perform the action that should repeat in the loop
- // This could be some mock behavior or just an empty loop depending on the context
- }
- })
- .Verifiable (Times.Once);
- }
- private void SetupRunInputMockMethodToBlock (Mock<INetInput> netInput)
- {
- netInput.Setup (r => r.Run (It.IsAny<CancellationToken> ()))
- .Callback<CancellationToken> (token =>
- {
- // Simulate an infinite loop that checks for cancellation
- while (!token.IsCancellationRequested)
- {
- // Perform the action that should repeat in the loop
- // This could be some mock behavior or just an empty loop depending on the context
- }
- })
- .Verifiable (Times.Once);
- }
- [Fact]
- public void NoInitThrowOnRun ()
- {
- var orig = ApplicationImpl.Instance;
- Assert.Null (Application.Driver);
- var app = NewApplicationImpl ();
- ApplicationImpl.ChangeInstance (app);
- var ex = Assert.Throws<NotInitializedException> (() => app.Run (new Window ()));
- Assert.Equal ("Run cannot be accessed before Initialization", ex.Message);
- app.Shutdown();
- ApplicationImpl.ChangeInstance (orig);
- }
- [Fact]
- public void InitRunShutdown_Top_Set_To_Null_After_Shutdown ()
- {
- var orig = ApplicationImpl.Instance;
- var v2 = NewApplicationImpl ();
- ApplicationImpl.ChangeInstance (v2);
- v2.Init ();
- var timeoutToken = v2.AddTimeout (TimeSpan.FromMilliseconds (150),
- () =>
- {
- if (Application.Top != null)
- {
- Application.RequestStop ();
- return false;
- }
- return false;
- }
- );
- Assert.Null (Application.Top);
- // Blocks until the timeout call is hit
- v2.Run (new Window ());
- // We returned false above, so we should not have to remove the timeout
- Assert.False(v2.RemoveTimeout (timeoutToken));
- Assert.NotNull (Application.Top);
- Application.Top?.Dispose ();
- v2.Shutdown ();
- Assert.Null (Application.Top);
- ApplicationImpl.ChangeInstance (orig);
- }
- [Fact]
- public void InitRunShutdown_Running_Set_To_False ()
- {
- var orig = ApplicationImpl.Instance;
- var v2 = NewApplicationImpl ();
- ApplicationImpl.ChangeInstance (v2);
- v2.Init ();
- Toplevel top = new Window ()
- {
- Title = "InitRunShutdown_Running_Set_To_False"
- };
- var timeoutToken = v2.AddTimeout (TimeSpan.FromMilliseconds (150),
- () =>
- {
- Assert.True (top!.Running);
- if (Application.Top != null)
- {
- Application.RequestStop ();
- return false;
- }
- return false;
- }
- );
- Assert.False (top!.Running);
- // Blocks until the timeout call is hit
- v2.Run (top);
- // We returned false above, so we should not have to remove the timeout
- Assert.False (v2.RemoveTimeout (timeoutToken));
- Assert.False (top!.Running);
- // BUGBUG: Shutdown sets Top to null, not End.
- //Assert.Null (Application.Top);
- Application.Top?.Dispose ();
- v2.Shutdown ();
- ApplicationImpl.ChangeInstance (orig);
- }
- [Fact]
- public void InitRunShutdown_End_Is_Called ()
- {
- var orig = ApplicationImpl.Instance;
- var v2 = NewApplicationImpl ();
- ApplicationImpl.ChangeInstance (v2);
- Assert.Null (Application.Top);
- Assert.Null (Application.Driver);
- v2.Init ();
- Toplevel top = new Window ();
- // BUGBUG: Both Closed and Unloaded are called from End; what's the difference?
- int closedCount = 0;
- top.Closed
- += (_, a) =>
- {
- closedCount++;
- };
- int unloadedCount = 0;
- top.Unloaded
- += (_, a) =>
- {
- unloadedCount++;
- };
- var timeoutToken = v2.AddTimeout (TimeSpan.FromMilliseconds (150),
- () =>
- {
- Assert.True (top!.Running);
- if (Application.Top != null)
- {
- Application.RequestStop ();
- return false;
- }
- return false;
- }
- );
- Assert.Equal (0, closedCount);
- Assert.Equal (0, unloadedCount);
- // Blocks until the timeout call is hit
- v2.Run (top);
- Assert.Equal (1, closedCount);
- Assert.Equal (1, unloadedCount);
- // We returned false above, so we should not have to remove the timeout
- Assert.False (v2.RemoveTimeout (timeoutToken));
- Application.Top?.Dispose ();
- v2.Shutdown ();
- Assert.Equal (1, closedCount);
- Assert.Equal (1, unloadedCount);
- ApplicationImpl.ChangeInstance (orig);
- }
- [Fact]
- public void InitRunShutdown_QuitKey_Quits ()
- {
- var orig = ApplicationImpl.Instance;
- var v2 = NewApplicationImpl ();
- ApplicationImpl.ChangeInstance (v2);
- v2.Init ();
- Toplevel top = new Window ()
- {
- Title = "InitRunShutdown_QuitKey_Quits"
- };
- var timeoutToken = v2.AddTimeout (TimeSpan.FromMilliseconds (150),
- () =>
- {
- Assert.True (top!.Running);
- if (Application.Top != null)
- {
- Application.RaiseKeyDownEvent (Application.QuitKey);
- }
- return false;
- }
- );
- Assert.False (top!.Running);
- // Blocks until the timeout call is hit
- v2.Run (top);
- // We returned false above, so we should not have to remove the timeout
- Assert.False (v2.RemoveTimeout (timeoutToken));
- Assert.False (top!.Running);
- Assert.NotNull (Application.Top);
- top.Dispose ();
- v2.Shutdown ();
- Assert.Null (Application.Top);
- ApplicationImpl.ChangeInstance (orig);
- }
- [Fact]
- public void InitRunShutdown_Generic_IdleForExit ()
- {
- var orig = ApplicationImpl.Instance;
- var v2 = NewApplicationImpl ();
- ApplicationImpl.ChangeInstance (v2);
- v2.Init ();
- v2.AddTimeout (TimeSpan.Zero, IdleExit);
- Assert.Null (Application.Top);
- // Blocks until the timeout call is hit
- v2.Run<Window> ();
- Assert.NotNull (Application.Top);
- Application.Top?.Dispose ();
- v2.Shutdown ();
- Assert.Null (Application.Top);
- ApplicationImpl.ChangeInstance (orig);
- }
- [Fact]
- public void Shutdown_Closing_Closed_Raised ()
- {
- var orig = ApplicationImpl.Instance;
- var v2 = NewApplicationImpl ();
- ApplicationImpl.ChangeInstance (v2);
- v2.Init ();
- int closing = 0;
- int closed = 0;
- var t = new Toplevel ();
- t.Closing
- += (_, a) =>
- {
- // Cancel the first time
- if (closing == 0)
- {
- a.Cancel = true;
- }
- closing++;
- Assert.Same (t, a.RequestingTop);
- };
- t.Closed
- += (_, a) =>
- {
- closed++;
- Assert.Same (t, a.Toplevel);
- };
- v2.AddTimeout(TimeSpan.Zero, IdleExit);
- // Blocks until the timeout call is hit
- v2.Run (t);
- Application.Top?.Dispose ();
- v2.Shutdown ();
- ApplicationImpl.ChangeInstance (orig);
- Assert.Equal (2, closing);
- Assert.Equal (1, closed);
- }
- private bool IdleExit ()
- {
- if (Application.Top != null)
- {
- Application.RequestStop ();
- return true;
- }
- return true;
- }
- /*
- [Fact]
- public void Shutdown_Called_Repeatedly_DoNotDuplicateDisposeOutput ()
- {
- var orig = ApplicationImpl.Instance;
- var netInput = new Mock<INetInput> ();
- SetupRunInputMockMethodToBlock (netInput);
- Mock<IConsoleOutput>? outputMock = null;
- var v2 = new ApplicationV2 (
- () => netInput.Object,
- () => (outputMock = new Mock<IConsoleOutput> ()).Object,
- Mock.Of<IWindowsInput>,
- Mock.Of<IConsoleOutput>);
- ApplicationImpl.ChangeInstance (v2);
- v2.Init (null, "v2net");
- v2.Shutdown ();
- outputMock!.Verify (o => o.Dispose (), Times.Once);
- ApplicationImpl.ChangeInstance (orig);
- }
- */
- [Fact]
- public void Open_Calls_ContinueWith_On_UIThread ()
- {
- var orig = ApplicationImpl.Instance;
- var v2 = NewApplicationImpl ();
- ApplicationImpl.ChangeInstance (v2);
- v2.Init ();
- var b = new Button ();
- bool result = false;
- b.Accepting +=
- (_, _) =>
- {
- Task.Run (() =>
- {
- Task.Delay (300).Wait ();
- }).ContinueWith (
- (t, _) =>
- {
- // no longer loading
- Application.Invoke (() =>
- {
- result = true;
- Application.RequestStop ();
- });
- },
- TaskScheduler.FromCurrentSynchronizationContext ());
- };
- v2.AddTimeout (TimeSpan.FromMilliseconds (150),
- () =>
- {
- // Run asynchronous logic inside Task.Run
- if (Application.Top != null)
- {
- b.NewKeyDownEvent (Key.Enter);
- b.NewKeyUpEvent (Key.Enter);
- }
- return false;
- });
- Assert.Null (Application.Top);
- var w = new Window ()
- {
- Title = "Open_CallsContinueWithOnUIThread"
- };
- w.Add (b);
- // Blocks until the timeout call is hit
- v2.Run (w);
- Assert.NotNull (Application.Top);
- Application.Top?.Dispose ();
- v2.Shutdown ();
- Assert.Null (Application.Top);
- ApplicationImpl.ChangeInstance (orig);
- Assert.True (result);
- }
- [Fact]
- public void ApplicationImpl_UsesInstanceFields_NotStaticReferences()
- {
- // This test verifies that ApplicationImpl uses instance fields instead of static Application references
- var orig = ApplicationImpl.Instance;
- var v2 = NewApplicationImpl();
- ApplicationImpl.ChangeInstance(v2);
- // Before Init, all fields should be null/default
- Assert.Null(v2.Driver);
- Assert.False(v2.Initialized);
- Assert.Null(v2.Popover);
- Assert.Null(v2.Navigation);
- Assert.Null(v2.Top);
- Assert.Empty(v2.TopLevels);
- // Init should populate instance fields
- v2.Init();
- // After Init, Driver, Navigation, and Popover should be populated
- Assert.NotNull(v2.Driver);
- Assert.True(v2.Initialized);
- Assert.NotNull(v2.Popover);
- Assert.NotNull(v2.Navigation);
- Assert.Null(v2.Top); // Top is still null until Run
- // Verify that static Application properties delegate to instance
- Assert.Equal(v2.Driver, Application.Driver);
- Assert.Equal(v2.Initialized, Application.Initialized);
- Assert.Equal(v2.Popover, Application.Popover);
- Assert.Equal(v2.Navigation, Application.Navigation);
- Assert.Equal(v2.Top, Application.Top);
- Assert.Same(v2.TopLevels, Application.TopLevels);
- // Shutdown should clean up instance fields
- v2.Shutdown();
- Assert.Null(v2.Driver);
- Assert.False(v2.Initialized);
- Assert.Null(v2.Popover);
- Assert.Null(v2.Navigation);
- Assert.Null(v2.Top);
- Assert.Empty(v2.TopLevels);
- ApplicationImpl.ChangeInstance(orig);
- }
- }
|