ApplicationV2Tests.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. using System.Collections.Concurrent;
  2. using Microsoft.Extensions.Logging;
  3. using Moq;
  4. namespace UnitTests.ConsoleDrivers.V2;
  5. public class ApplicationV2Tests
  6. {
  7. private ApplicationV2 NewApplicationV2 ()
  8. {
  9. var netInput = new Mock<INetInput> ();
  10. SetupRunInputMockMethodToBlock (netInput);
  11. var winInput = new Mock<IWindowsInput> ();
  12. SetupRunInputMockMethodToBlock (winInput);
  13. return new (
  14. ()=>netInput.Object,
  15. Mock.Of<IConsoleOutput>,
  16. () => winInput.Object,
  17. Mock.Of<IConsoleOutput>);
  18. }
  19. [Fact]
  20. public void TestInit_CreatesKeybindings ()
  21. {
  22. var v2 = NewApplicationV2();
  23. Application.KeyBindings.Clear();
  24. Assert.Empty(Application.KeyBindings.GetBindings ());
  25. v2.Init ();
  26. Assert.NotEmpty (Application.KeyBindings.GetBindings ());
  27. v2.Shutdown ();
  28. }
  29. [Fact]
  30. public void TestInit_DriverIsFacade ()
  31. {
  32. var v2 = NewApplicationV2();
  33. Assert.Null (Application.Driver);
  34. v2.Init ();
  35. Assert.NotNull (Application.Driver);
  36. var type = Application.Driver.GetType ();
  37. Assert.True(type.IsGenericType);
  38. Assert.True (type.GetGenericTypeDefinition () == typeof (ConsoleDriverFacade<>));
  39. v2.Shutdown ();
  40. Assert.Null (Application.Driver);
  41. }
  42. [Fact]
  43. public void TestInit_ExplicitlyRequestWin ()
  44. {
  45. var netInput = new Mock<INetInput> (MockBehavior.Strict);
  46. var netOutput = new Mock<IConsoleOutput> (MockBehavior.Strict);
  47. var winInput = new Mock<IWindowsInput> (MockBehavior.Strict);
  48. var winOutput = new Mock<IConsoleOutput> (MockBehavior.Strict);
  49. winInput.Setup (i => i.Initialize (It.IsAny<ConcurrentQueue<WindowsConsole.InputRecord>> ()))
  50. .Verifiable(Times.Once);
  51. SetupRunInputMockMethodToBlock (winInput);
  52. winInput.Setup (i=>i.Dispose ())
  53. .Verifiable(Times.Once);
  54. winOutput.Setup (i => i.Dispose ())
  55. .Verifiable (Times.Once);
  56. var v2 = new ApplicationV2 (
  57. ()=> netInput.Object,
  58. () => netOutput.Object,
  59. () => winInput.Object,
  60. () => winOutput.Object);
  61. Assert.Null (Application.Driver);
  62. v2.Init (null,"v2win");
  63. Assert.NotNull (Application.Driver);
  64. var type = Application.Driver.GetType ();
  65. Assert.True (type.IsGenericType);
  66. Assert.True (type.GetGenericTypeDefinition () == typeof (ConsoleDriverFacade<>));
  67. v2.Shutdown ();
  68. Assert.Null (Application.Driver);
  69. winInput.VerifyAll();
  70. }
  71. [Fact]
  72. public void TestInit_ExplicitlyRequestNet ()
  73. {
  74. var netInput = new Mock<INetInput> (MockBehavior.Strict);
  75. var netOutput = new Mock<IConsoleOutput> (MockBehavior.Strict);
  76. var winInput = new Mock<IWindowsInput> (MockBehavior.Strict);
  77. var winOutput = new Mock<IConsoleOutput> (MockBehavior.Strict);
  78. netInput.Setup (i => i.Initialize (It.IsAny<ConcurrentQueue<ConsoleKeyInfo>> ()))
  79. .Verifiable (Times.Once);
  80. SetupRunInputMockMethodToBlock (netInput);
  81. netInput.Setup (i => i.Dispose ())
  82. .Verifiable (Times.Once);
  83. netOutput.Setup (i => i.Dispose ())
  84. .Verifiable (Times.Once);
  85. var v2 = new ApplicationV2 (
  86. () => netInput.Object,
  87. () => netOutput.Object,
  88. () => winInput.Object,
  89. () => winOutput.Object);
  90. Assert.Null (Application.Driver);
  91. v2.Init (null, "v2net");
  92. Assert.NotNull (Application.Driver);
  93. var type = Application.Driver.GetType ();
  94. Assert.True (type.IsGenericType);
  95. Assert.True (type.GetGenericTypeDefinition () == typeof (ConsoleDriverFacade<>));
  96. v2.Shutdown ();
  97. Assert.Null (Application.Driver);
  98. netInput.VerifyAll ();
  99. }
  100. private void SetupRunInputMockMethodToBlock (Mock<IWindowsInput> winInput)
  101. {
  102. winInput.Setup (r => r.Run (It.IsAny<CancellationToken> ()))
  103. .Callback<CancellationToken> (token =>
  104. {
  105. // Simulate an infinite loop that checks for cancellation
  106. while (!token.IsCancellationRequested)
  107. {
  108. // Perform the action that should repeat in the loop
  109. // This could be some mock behavior or just an empty loop depending on the context
  110. }
  111. })
  112. .Verifiable (Times.Once);
  113. }
  114. private void SetupRunInputMockMethodToBlock (Mock<INetInput> netInput)
  115. {
  116. netInput.Setup (r => r.Run (It.IsAny<CancellationToken> ()))
  117. .Callback<CancellationToken> (token =>
  118. {
  119. // Simulate an infinite loop that checks for cancellation
  120. while (!token.IsCancellationRequested)
  121. {
  122. // Perform the action that should repeat in the loop
  123. // This could be some mock behavior or just an empty loop depending on the context
  124. }
  125. })
  126. .Verifiable (Times.Once);
  127. }
  128. [Fact]
  129. public void Test_NoInitThrowOnRun ()
  130. {
  131. var app = NewApplicationV2();
  132. var ex = Assert.Throws<NotInitializedException> (() => app.Run (new Window ()));
  133. Assert.Equal ("Run cannot be accessed before Initialization", ex.Message);
  134. }
  135. [Fact]
  136. public void Test_InitRunShutdown ()
  137. {
  138. var orig = ApplicationImpl.Instance;
  139. var v2 = NewApplicationV2();
  140. ApplicationImpl.ChangeInstance (v2);
  141. v2.Init ();
  142. var timeoutToken = v2.AddTimeout (TimeSpan.FromMilliseconds (150),
  143. () =>
  144. {
  145. if (Application.Top != null)
  146. {
  147. Application.RequestStop ();
  148. return true;
  149. }
  150. return true;
  151. }
  152. );
  153. Assert.Null (Application.Top);
  154. // Blocks until the timeout call is hit
  155. v2.Run (new Window ());
  156. Assert.True(v2.RemoveTimeout (timeoutToken));
  157. Assert.Null (Application.Top);
  158. v2.Shutdown ();
  159. ApplicationImpl.ChangeInstance (orig);
  160. }
  161. [Fact]
  162. public void Test_InitRunShutdown_Generic_IdleForExit ()
  163. {
  164. var orig = ApplicationImpl.Instance;
  165. var v2 = NewApplicationV2 ();
  166. ApplicationImpl.ChangeInstance (v2);
  167. v2.Init ();
  168. v2.AddIdle (IdleExit);
  169. Assert.Null (Application.Top);
  170. // Blocks until the timeout call is hit
  171. v2.Run<Window> ();
  172. Assert.Null (Application.Top);
  173. v2.Shutdown ();
  174. ApplicationImpl.ChangeInstance (orig);
  175. }
  176. private bool IdleExit ()
  177. {
  178. if (Application.Top != null)
  179. {
  180. Application.RequestStop ();
  181. return true;
  182. }
  183. return true;
  184. }
  185. [Fact]
  186. public void TestRepeatedShutdownCalls_DoNotDuplicateDisposeOutput ()
  187. {
  188. var netInput = new Mock<INetInput> ();
  189. SetupRunInputMockMethodToBlock (netInput);
  190. Mock<IConsoleOutput>? outputMock = null;
  191. var v2 = new ApplicationV2(
  192. () => netInput.Object,
  193. ()=> (outputMock = new Mock<IConsoleOutput>()).Object,
  194. Mock.Of<IWindowsInput>,
  195. Mock.Of<IConsoleOutput>);
  196. v2.Init (null,"v2net");
  197. v2.Shutdown ();
  198. v2.Shutdown ();
  199. outputMock.Verify(o=>o.Dispose (),Times.Once);
  200. }
  201. [Fact]
  202. public void TestRepeatedInitCalls_WarnsAndIgnores ()
  203. {
  204. var v2 = NewApplicationV2 ();
  205. Assert.Null (Application.Driver);
  206. v2.Init ();
  207. Assert.NotNull (Application.Driver);
  208. var mockLogger = new Mock<ILogger> ();
  209. var beforeLogger = Logging.Logger;
  210. Logging.Logger = mockLogger.Object;
  211. v2.Init ();
  212. v2.Init ();
  213. mockLogger.Verify(
  214. l=>l.Log (LogLevel.Error,
  215. It.IsAny<EventId> (),
  216. It.Is<It.IsAnyType> ((v, t) => v.ToString () == "Init called multiple times without shutdown, ignoring."),
  217. It.IsAny<Exception> (),
  218. It.IsAny<Func<It.IsAnyType, Exception, string>> ())
  219. ,Times.Exactly (2));
  220. v2.Shutdown ();
  221. // Restore the original null logger to be polite to other tests
  222. Logging.Logger = beforeLogger;
  223. }
  224. [Fact]
  225. public void Test_Open_CallsContinueWithOnUIThread ()
  226. {
  227. var orig = ApplicationImpl.Instance;
  228. var v2 = NewApplicationV2 ();
  229. ApplicationImpl.ChangeInstance (v2);
  230. v2.Init ();
  231. var b = new Button ();
  232. bool result = false;
  233. b.Accepting +=
  234. (_,_) =>
  235. {
  236. Task.Run (() =>
  237. {
  238. Task.Delay (300).Wait ();
  239. }).ContinueWith (
  240. (t, _) =>
  241. {
  242. // no longer loading
  243. Application.Invoke (() =>
  244. {
  245. result = true;
  246. Application.RequestStop ();
  247. });
  248. },
  249. TaskScheduler.FromCurrentSynchronizationContext ());
  250. };
  251. v2.AddTimeout (TimeSpan.FromMilliseconds (150),
  252. ()=>
  253. {
  254. // Run asynchronous logic inside Task.Run
  255. if (Application.Top != null)
  256. {
  257. b.NewKeyDownEvent (Key.Enter);
  258. b.NewKeyUpEvent (Key.Enter);
  259. return false;
  260. }
  261. return true;
  262. });
  263. Assert.Null (Application.Top);
  264. var w = new Window ();
  265. w.Add (b);
  266. // Blocks until the timeout call is hit
  267. v2.Run (w);
  268. Assert.Null (Application.Top);
  269. v2.Shutdown ();
  270. ApplicationImpl.ChangeInstance (orig);
  271. Assert.True (result);
  272. }
  273. }