#nullable enable using System.Collections.Concurrent; using System.Runtime.CompilerServices; using Microsoft.Extensions.Logging; using Moq; namespace UnitTests.ConsoleDrivers.V2; public class ApplicationV2Tests { private ApplicationV2 NewApplicationV2 () { var netInput = new Mock (); SetupRunInputMockMethodToBlock (netInput); var winInput = new Mock (); SetupRunInputMockMethodToBlock (winInput); return new ( () => netInput.Object, Mock.Of, () => winInput.Object, Mock.Of); } [Fact] public void Init_CreatesKeybindings () { var v2 = NewApplicationV2 (); Application.KeyBindings.Clear (); Assert.Empty (Application.KeyBindings.GetBindings ()); v2.Init (); Assert.NotEmpty (Application.KeyBindings.GetBindings ()); v2.Shutdown (); } [Fact] public void Init_DriverIsFacade () { var v2 = NewApplicationV2 (); 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); } [Fact] public void Init_ExplicitlyRequestWin () { Assert.Null (Application.Driver); var netInput = new Mock (MockBehavior.Strict); var netOutput = new Mock (MockBehavior.Strict); var winInput = new Mock (MockBehavior.Strict); var winOutput = new Mock (MockBehavior.Strict); winInput.Setup (i => i.Initialize (It.IsAny> ())) .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); 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 (); } [Fact] public void Init_ExplicitlyRequestNet () { var netInput = new Mock (MockBehavior.Strict); var netOutput = new Mock (MockBehavior.Strict); var winInput = new Mock (MockBehavior.Strict); var winOutput = new Mock (MockBehavior.Strict); netInput.Setup (i => i.Initialize (It.IsAny> ())) .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); 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 (); } private void SetupRunInputMockMethodToBlock (Mock winInput) { winInput.Setup (r => r.Run (It.IsAny ())) .Callback (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 netInput) { netInput.Setup (r => r.Run (It.IsAny ())) .Callback (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 () { Assert.Null (Application.Driver); var app = NewApplicationV2 (); var ex = Assert.Throws (() => app.Run (new Window ())); Assert.Equal ("Run cannot be accessed before Initialization", ex.Message); app.Shutdown(); } [Fact] public void InitRunShutdown_Top_Set_To_Null_After_Shutdown () { var orig = ApplicationImpl.Instance; var v2 = NewApplicationV2 (); ApplicationImpl.ChangeInstance (v2); v2.Init (); var timeoutToken = v2.AddTimeout (TimeSpan.FromMilliseconds (150), () => { if (Application.Top != null) { Application.RequestStop (); return true; } return true; } ); Assert.Null (Application.Top); // Blocks until the timeout call is hit v2.Run (new Window ()); Assert.True (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 = NewApplicationV2 (); 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 true; } return true; } ); Assert.False (top!.Running); // Blocks until the timeout call is hit v2.Run (top); Assert.True (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 = NewApplicationV2 (); 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 true; } return true; } ); 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); Assert.True (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 = NewApplicationV2 (); 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 true; } return true; } ); Assert.False (top!.Running); // Blocks until the timeout call is hit v2.Run (top); Assert.True (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 = NewApplicationV2 (); ApplicationImpl.ChangeInstance (v2); v2.Init (); v2.AddIdle (IdleExit); Assert.Null (Application.Top); // Blocks until the timeout call is hit v2.Run (); 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 = NewApplicationV2 (); 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.AddIdle (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 netInput = new Mock (); SetupRunInputMockMethodToBlock (netInput); Mock? outputMock = null; var v2 = new ApplicationV2 ( () => netInput.Object, () => (outputMock = new Mock ()).Object, Mock.Of, Mock.Of); v2.Init (null, "v2net"); v2.Shutdown (); v2.Shutdown (); outputMock!.Verify (o => o.Dispose (), Times.Once); } [Fact] public void Init_Called_Repeatedly_WarnsAndIgnores () { var v2 = NewApplicationV2 (); Assert.Null (Application.Driver); v2.Init (); Assert.NotNull (Application.Driver); var mockLogger = new Mock (); var beforeLogger = Logging.Logger; Logging.Logger = mockLogger.Object; v2.Init (); v2.Init (); mockLogger.Verify ( l => l.Log (LogLevel.Error, It.IsAny (), It.Is ((v, t) => v.ToString () == "Init called multiple times without shutdown, ignoring."), It.IsAny (), It.IsAny> ()!) , Times.Exactly (2)); v2.Shutdown (); // Restore the original null logger to be polite to other tests Logging.Logger = beforeLogger; } // QUESTION: What does this test really test? It's poorly named. [Fact] public void Open_CallsContinueWithOnUIThread () { var orig = ApplicationImpl.Instance; var v2 = NewApplicationV2 (); 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; } return true; }); 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); } }