ApplicationV2Tests.cs 13 KB

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