ApplicationTests.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. using System;
  2. using System.Diagnostics;
  3. using System.Linq;
  4. using System.Runtime.InteropServices;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using Xunit;
  8. using Xunit.Abstractions;
  9. // Alias Console to MockConsole so we don't accidentally use Console
  10. using Console = Terminal.Gui.FakeConsole;
  11. namespace Terminal.Gui.ApplicationTests;
  12. public class ApplicationTests {
  13. readonly ITestOutputHelper _output;
  14. public ApplicationTests (ITestOutputHelper output)
  15. {
  16. this._output = output;
  17. #if DEBUG_IDISPOSABLE
  18. Responder.Instances.Clear ();
  19. RunState.Instances.Clear ();
  20. #endif
  21. }
  22. void Pre_Init_State ()
  23. {
  24. Assert.Null (Application.Driver);
  25. Assert.Null (Application.Top);
  26. Assert.Null (Application.Current);
  27. Assert.Null (Application.MainLoop);
  28. }
  29. void Post_Init_State ()
  30. {
  31. Assert.NotNull (Application.Driver);
  32. Assert.NotNull (Application.Top);
  33. Assert.NotNull (Application.Current);
  34. Assert.NotNull (Application.MainLoop);
  35. // FakeDriver is always 80x25
  36. Assert.Equal (80, Application.Driver.Cols);
  37. Assert.Equal (25, Application.Driver.Rows);
  38. }
  39. void Init ()
  40. {
  41. Application.Init (new FakeDriver ());
  42. Assert.NotNull (Application.Driver);
  43. Assert.NotNull (Application.MainLoop);
  44. Assert.NotNull (SynchronizationContext.Current);
  45. }
  46. void Shutdown ()
  47. {
  48. Application.Shutdown ();
  49. }
  50. [Fact]
  51. public void Init_Shutdown_Cleans_Up ()
  52. {
  53. // Verify initial state is per spec
  54. //Pre_Init_State ();
  55. Application.Init (new FakeDriver ());
  56. // Verify post-Init state is correct
  57. //Post_Init_State ();
  58. Application.Shutdown ();
  59. // Verify state is back to initial
  60. //Pre_Init_State ();
  61. #if DEBUG_IDISPOSABLE
  62. // Validate there are no outstanding Responder-based instances
  63. // after a scenario was selected to run. This proves the main UI Catalog
  64. // 'app' closed cleanly.
  65. Assert.Empty (Responder.Instances);
  66. #endif
  67. }
  68. [Fact]
  69. public void Init_Unbalanced_Throws ()
  70. {
  71. Application.Init (new FakeDriver ());
  72. Toplevel topLevel = null;
  73. Assert.Throws<InvalidOperationException> (() => Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver ()));
  74. Shutdown ();
  75. Assert.Null (Application.Top);
  76. Assert.Null (Application.MainLoop);
  77. Assert.Null (Application.Driver);
  78. // Now try the other way
  79. topLevel = null;
  80. Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver ());
  81. Assert.Throws<InvalidOperationException> (() => Application.Init (new FakeDriver ()));
  82. Shutdown ();
  83. Assert.Null (Application.Top);
  84. Assert.Null (Application.MainLoop);
  85. Assert.Null (Application.Driver);
  86. }
  87. class TestToplevel : Toplevel {
  88. public TestToplevel ()
  89. {
  90. IsOverlappedContainer = false;
  91. }
  92. }
  93. [Fact]
  94. public void Init_Null_Driver_Should_Pick_A_Driver ()
  95. {
  96. Application.Init (null);
  97. Assert.NotNull (Application.Driver);
  98. Shutdown ();
  99. }
  100. [Fact]
  101. public void Init_Begin_End_Cleans_Up ()
  102. {
  103. Init ();
  104. // Begin will cause Run() to be called, which will call Begin(). Thus will block the tests
  105. // if we don't stop
  106. Application.Iteration += (s, a) => {
  107. Application.RequestStop ();
  108. };
  109. RunState runstate = null;
  110. EventHandler<RunStateEventArgs> NewRunStateFn = (s, e) => {
  111. Assert.NotNull (e.State);
  112. runstate = e.State;
  113. };
  114. Application.NotifyNewRunState += NewRunStateFn;
  115. Toplevel topLevel = new Toplevel ();
  116. var rs = Application.Begin (topLevel);
  117. Assert.NotNull (rs);
  118. Assert.NotNull (runstate);
  119. Assert.Equal (rs, runstate);
  120. Assert.Equal (topLevel, Application.Top);
  121. Assert.Equal (topLevel, Application.Current);
  122. Application.NotifyNewRunState -= NewRunStateFn;
  123. Application.End (runstate);
  124. Assert.Null (Application.Current);
  125. Assert.NotNull (Application.Top);
  126. Assert.NotNull (Application.MainLoop);
  127. Assert.NotNull (Application.Driver);
  128. Shutdown ();
  129. Assert.Null (Application.Top);
  130. Assert.Null (Application.MainLoop);
  131. Assert.Null (Application.Driver);
  132. }
  133. [Fact]
  134. public void InitWithTopLevelFactory_Begin_End_Cleans_Up ()
  135. {
  136. // Begin will cause Run() to be called, which will call Begin(). Thus will block the tests
  137. // if we don't stop
  138. Application.Iteration += (s, a) => {
  139. Application.RequestStop ();
  140. };
  141. // NOTE: Run<T>, when called after Init has been called behaves differently than
  142. // when called if Init has not been called.
  143. Toplevel topLevel = null;
  144. Application.InternalInit (() => topLevel = new TestToplevel (), new FakeDriver ());
  145. RunState runstate = null;
  146. EventHandler<RunStateEventArgs> NewRunStateFn = (s, e) => {
  147. Assert.NotNull (e.State);
  148. runstate = e.State;
  149. };
  150. Application.NotifyNewRunState += NewRunStateFn;
  151. var rs = Application.Begin (topLevel);
  152. Assert.NotNull (rs);
  153. Assert.NotNull (runstate);
  154. Assert.Equal (rs, runstate);
  155. Assert.Equal (topLevel, Application.Top);
  156. Assert.Equal (topLevel, Application.Current);
  157. Application.NotifyNewRunState -= NewRunStateFn;
  158. Application.End (runstate);
  159. Assert.Null (Application.Current);
  160. Assert.NotNull (Application.Top);
  161. Assert.NotNull (Application.MainLoop);
  162. Assert.NotNull (Application.Driver);
  163. Shutdown ();
  164. Assert.Null (Application.Top);
  165. Assert.Null (Application.MainLoop);
  166. Assert.Null (Application.Driver);
  167. }
  168. [Fact]
  169. public void Begin_Null_Toplevel_Throws ()
  170. {
  171. // Setup Mock driver
  172. Init ();
  173. // Test null Toplevel
  174. Assert.Throws<ArgumentNullException> (() => Application.Begin (null));
  175. Shutdown ();
  176. Assert.Null (Application.Top);
  177. Assert.Null (Application.MainLoop);
  178. Assert.Null (Application.Driver);
  179. }
  180. #region RunTests
  181. [Fact]
  182. public void Run_T_After_InitWithDriver_with_TopLevel_Throws ()
  183. {
  184. // Setup Mock driver
  185. Init ();
  186. // Run<Toplevel> when already initialized with a Driver will throw (because Toplevel is not dervied from TopLevel)
  187. Assert.Throws<ArgumentException> (() => Application.Run<Toplevel> (errorHandler: null));
  188. Shutdown ();
  189. Assert.Null (Application.Top);
  190. Assert.Null (Application.MainLoop);
  191. Assert.Null (Application.Driver);
  192. }
  193. [Fact]
  194. public void Run_T_After_InitWithDriver_with_TopLevel_and_Driver_Throws ()
  195. {
  196. // Setup Mock driver
  197. Init ();
  198. // Run<Toplevel> when already initialized with a Driver will throw (because Toplevel is not dervied from TopLevel)
  199. Assert.Throws<ArgumentException> (() => Application.Run<Toplevel> (errorHandler: null, new FakeDriver ()));
  200. Shutdown ();
  201. Assert.Null (Application.Top);
  202. Assert.Null (Application.MainLoop);
  203. Assert.Null (Application.Driver);
  204. }
  205. [Fact]
  206. public void Run_T_After_InitWithDriver_with_TestTopLevel_DoesNotThrow ()
  207. {
  208. // Setup Mock driver
  209. Init ();
  210. Application.Iteration += (s, a) => {
  211. Application.RequestStop ();
  212. };
  213. // Init has been called and we're passing no driver to Run<TestTopLevel>. This is ok.
  214. Application.Run<TestToplevel> ();
  215. Shutdown ();
  216. Assert.Null (Application.Top);
  217. Assert.Null (Application.MainLoop);
  218. Assert.Null (Application.Driver);
  219. }
  220. [Fact]
  221. public void Run_T_After_InitNullDriver_with_TestTopLevel_Throws ()
  222. {
  223. Application._forceFakeConsole = true;
  224. Application.Init (null);
  225. Assert.Equal (typeof (FakeDriver), Application.Driver.GetType ());
  226. Application.Iteration += (s, a) => {
  227. Application.RequestStop ();
  228. };
  229. // Init has been called without selecting a driver and we're passing no driver to Run<TestTopLevel>. Bad
  230. Application.Run<TestToplevel> ();
  231. Shutdown ();
  232. Assert.Null (Application.Top);
  233. Assert.Null (Application.MainLoop);
  234. Assert.Null (Application.Driver);
  235. }
  236. [Fact]
  237. public void Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws ()
  238. {
  239. Init ();
  240. Application.Driver = null;
  241. Application.Iteration += (s, a) => {
  242. Application.RequestStop ();
  243. };
  244. // Init has been called, but Driver has been set to null. Bad.
  245. Assert.Throws<InvalidOperationException> (() => Application.Run<TestToplevel> ());
  246. Shutdown ();
  247. Assert.Null (Application.Top);
  248. Assert.Null (Application.MainLoop);
  249. Assert.Null (Application.Driver);
  250. }
  251. [Fact]
  252. public void Run_T_NoInit_DoesNotThrow ()
  253. {
  254. Application._forceFakeConsole = true;
  255. Application.Iteration += (s, a) => {
  256. Application.RequestStop ();
  257. };
  258. Application.Run<TestToplevel> ();
  259. Assert.Equal (typeof (FakeDriver), Application.Driver.GetType ());
  260. Shutdown ();
  261. Assert.Null (Application.Top);
  262. Assert.Null (Application.MainLoop);
  263. Assert.Null (Application.Driver);
  264. }
  265. [Fact]
  266. public void Run_T_NoInit_WithDriver_DoesNotThrow ()
  267. {
  268. Application.Iteration += (s, a) => {
  269. Application.RequestStop ();
  270. };
  271. // Init has NOT been called and we're passing a valid driver to Run<TestTopLevel>. This is ok.
  272. Application.Run<TestToplevel> (errorHandler: null, new FakeDriver ());
  273. Shutdown ();
  274. Assert.Null (Application.Top);
  275. Assert.Null (Application.MainLoop);
  276. Assert.Null (Application.Driver);
  277. }
  278. [Fact]
  279. public void Run_RequestStop_Stops ()
  280. {
  281. // Setup Mock driver
  282. Init ();
  283. var top = new Toplevel ();
  284. var rs = Application.Begin (top);
  285. Assert.NotNull (rs);
  286. Assert.Equal (top, Application.Current);
  287. Application.Iteration += (s, a) => {
  288. Application.RequestStop ();
  289. };
  290. Application.Run (top);
  291. Application.Shutdown ();
  292. Assert.Null (Application.Current);
  293. Assert.Null (Application.Top);
  294. Assert.Null (Application.MainLoop);
  295. Assert.Null (Application.Driver);
  296. }
  297. [Fact]
  298. public void Run_RunningFalse_Stops ()
  299. {
  300. // Setup Mock driver
  301. Init ();
  302. var top = new Toplevel ();
  303. var rs = Application.Begin (top);
  304. Assert.NotNull (rs);
  305. Assert.Equal (top, Application.Current);
  306. Application.Iteration += (s, a) => {
  307. top.Running = false;
  308. };
  309. Application.Run (top);
  310. Application.Shutdown ();
  311. Assert.Null (Application.Current);
  312. Assert.Null (Application.Top);
  313. Assert.Null (Application.MainLoop);
  314. Assert.Null (Application.Driver);
  315. }
  316. [Fact]
  317. public void Run_Loaded_Ready_Unlodaded_Events ()
  318. {
  319. Init ();
  320. var top = Application.Top;
  321. var count = 0;
  322. top.Loaded += (s, e) => count++;
  323. top.Ready += (s, e) => count++;
  324. top.Unloaded += (s, e) => count++;
  325. Application.Iteration += (s, a) => Application.RequestStop ();
  326. Application.Run ();
  327. Application.Shutdown ();
  328. Assert.Equal (3, count);
  329. }
  330. // TODO: All Toplevel layout tests should be moved to ToplevelTests.cs
  331. [Fact]
  332. public void Run_Toplevel_With_Modal_View_Does_Not_Refresh_If_Not_Dirty ()
  333. {
  334. Init ();
  335. var count = 0;
  336. // Don't use Dialog here as it has more layout logic. Use Window instead.
  337. Dialog d = null;
  338. var top = Application.Top;
  339. top.DrawContent += (s, a) => count++;
  340. var iteration = -1;
  341. Application.Iteration += (s, a) => {
  342. iteration++;
  343. if (iteration == 0) {
  344. // TODO: Don't use Dialog here as it has more layout logic. Use Window instead.
  345. d = new Dialog ();
  346. d.DrawContent += (s, a) => count++;
  347. Application.Run (d);
  348. } else if (iteration < 3) {
  349. Application.OnMouseEvent (new (new () { X = 0, Y = 0, Flags = MouseFlags.ReportMousePosition }));
  350. Assert.False (top.NeedsDisplay);
  351. Assert.False (top.SubViewNeedsDisplay);
  352. Assert.False (top.LayoutNeeded);
  353. Assert.False (d.NeedsDisplay);
  354. Assert.False (d.SubViewNeedsDisplay);
  355. Assert.False (d.LayoutNeeded);
  356. } else {
  357. Application.RequestStop ();
  358. }
  359. };
  360. Application.Run ();
  361. Application.Shutdown ();
  362. // 1 - First top load, 1 - Dialog load, 1 - Dialog unload, Total - 3.
  363. Assert.Equal (3, count);
  364. }
  365. // TODO: All Toplevel layout tests should be moved to ToplevelTests.cs
  366. [Fact]
  367. public void Run_A_Modal_Toplevel_Refresh_Background_On_Moving ()
  368. {
  369. Init ();
  370. // Don't use Dialog here as it has more layout logic. Use Window instead.
  371. var w = new Window () { Width = 5, Height = 5 };
  372. ((FakeDriver)Application.Driver).SetBufferSize (10, 10);
  373. var rs = Application.Begin (w);
  374. TestHelpers.AssertDriverContentsWithFrameAre (@"
  375. ┌───┐
  376. │ │
  377. │ │
  378. │ │
  379. └───┘", _output);
  380. var attributes = new Attribute [] {
  381. // 0
  382. new Attribute (ColorName.White, ColorName.Black),
  383. // 1
  384. Colors.Base.Normal
  385. };
  386. TestHelpers.AssertDriverColorsAre (@"
  387. 1111100000
  388. 1111100000
  389. 1111100000
  390. 1111100000
  391. 1111100000
  392. ", null, attributes);
  393. // TODO: In PR #2920 this breaks because the mouse is not grabbed anymore.
  394. // TODO: Move the mouse grap/drag mode from Toplevel to Border.
  395. Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { X = 0, Y = 0, Flags = MouseFlags.Button1Pressed }));
  396. Assert.Equal (w, Application.MouseGrabView);
  397. // Move down and to the right.
  398. Application.OnMouseEvent (new MouseEventEventArgs (new MouseEvent () { X = 1, Y = 1, Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition }));
  399. Application.Refresh ();
  400. TestHelpers.AssertDriverContentsWithFrameAre (@"
  401. ┌───┐
  402. │ │
  403. │ │
  404. │ │
  405. └───┘", _output);
  406. attributes = new Attribute [] {
  407. // 0
  408. new Attribute (ColorName.White, ColorName.Black),
  409. // 1
  410. Colors.Base.Normal
  411. };
  412. TestHelpers.AssertDriverColorsAre (@"
  413. 0000000000
  414. 0111110000
  415. 0111110000
  416. 0111110000
  417. 0111110000
  418. 0111110000
  419. ", null, attributes);
  420. Application.End (rs);
  421. Application.Shutdown ();
  422. }
  423. // TODO: Add tests for Run that test errorHandler
  424. #endregion
  425. #region ShutdownTests
  426. [Fact]
  427. public async void Shutdown_Allows_Async ()
  428. {
  429. bool isCompletedSuccessfully = false;
  430. async Task TaskWithAsyncContinuation ()
  431. {
  432. await Task.Yield ();
  433. await Task.Yield ();
  434. isCompletedSuccessfully = true;
  435. }
  436. Init ();
  437. Application.Shutdown ();
  438. Assert.False (isCompletedSuccessfully);
  439. await TaskWithAsyncContinuation ();
  440. Thread.Sleep (100);
  441. Assert.True (isCompletedSuccessfully);
  442. }
  443. [Fact]
  444. public void Shutdown_Resets_SyncContext ()
  445. {
  446. Init ();
  447. Application.Shutdown ();
  448. Assert.Null (SynchronizationContext.Current);
  449. }
  450. #endregion
  451. [Fact, AutoInitShutdown]
  452. public void Begin_Sets_Application_Top_To_Console_Size ()
  453. {
  454. Assert.Equal (new Rect (0, 0, 80, 25), Application.Top.Frame);
  455. ((FakeDriver)Application.Driver).SetBufferSize (5, 5);
  456. Application.Begin (Application.Top);
  457. Assert.Equal (new Rect (0, 0, 80, 25), Application.Top.Frame);
  458. ((FakeDriver)Application.Driver).SetBufferSize (5, 5);
  459. Assert.Equal (new Rect (0, 0, 5, 5), Application.Top.Frame);
  460. }
  461. [Fact]
  462. [AutoInitShutdown]
  463. public void SetCurrentAsTop_Run_A_Not_Modal_Toplevel_Make_It_The_Current_Application_Top ()
  464. {
  465. var t1 = new Toplevel ();
  466. var t2 = new Toplevel ();
  467. var t3 = new Toplevel ();
  468. // Don't use Dialog here as it has more layout logic. Use Window instead.
  469. var d = new Dialog ();
  470. var t4 = new Toplevel ();
  471. // t1, t2, t3, d, t4
  472. var iterations = 5;
  473. t1.Ready += (s, e) => {
  474. Assert.Equal (t1, Application.Top);
  475. Application.Run (t2);
  476. };
  477. t2.Ready += (s, e) => {
  478. Assert.Equal (t2, Application.Top);
  479. Application.Run (t3);
  480. };
  481. t3.Ready += (s, e) => {
  482. Assert.Equal (t3, Application.Top);
  483. Application.Run (d);
  484. };
  485. d.Ready += (s, e) => {
  486. Assert.Equal (t3, Application.Top);
  487. Application.Run (t4);
  488. };
  489. t4.Ready += (s, e) => {
  490. Assert.Equal (t4, Application.Top);
  491. t4.RequestStop ();
  492. d.RequestStop ();
  493. t3.RequestStop ();
  494. t2.RequestStop ();
  495. };
  496. // Now this will close the OverlappedContainer when all OverlappedChildren was closed
  497. t2.Closed += (s, _) => {
  498. t1.RequestStop ();
  499. };
  500. Application.Iteration += (s, a) => {
  501. if (iterations == 5) {
  502. // The Current still is t4 because Current.Running is false.
  503. Assert.Equal (t4, Application.Current);
  504. Assert.False (Application.Current.Running);
  505. Assert.Equal (t4, Application.Top);
  506. } else if (iterations == 4) {
  507. // The Current is d and Current.Running is false.
  508. Assert.Equal (d, Application.Current);
  509. Assert.False (Application.Current.Running);
  510. Assert.Equal (t4, Application.Top);
  511. } else if (iterations == 3) {
  512. // The Current is t3 and Current.Running is false.
  513. Assert.Equal (t3, Application.Current);
  514. Assert.False (Application.Current.Running);
  515. Assert.Equal (t3, Application.Top);
  516. } else if (iterations == 2) {
  517. // The Current is t2 and Current.Running is false.
  518. Assert.Equal (t2, Application.Current);
  519. Assert.False (Application.Current.Running);
  520. Assert.Equal (t2, Application.Top);
  521. } else {
  522. // The Current is t1.
  523. Assert.Equal (t1, Application.Current);
  524. Assert.False (Application.Current.Running);
  525. Assert.Equal (t1, Application.Top);
  526. }
  527. iterations--;
  528. };
  529. Application.Run (t1);
  530. Assert.Equal (t1, Application.Top);
  531. }
  532. [Fact]
  533. [AutoInitShutdown]
  534. public void Internal_Properties_Correct ()
  535. {
  536. Assert.True (Application._initialized);
  537. Assert.NotNull (Application.Top);
  538. var rs = Application.Begin (Application.Top);
  539. Assert.Equal (Application.Top, rs.Toplevel);
  540. Assert.Null (Application.MouseGrabView); // public
  541. Assert.Null (Application.WantContinuousButtonPressedView); // public
  542. Assert.False (Application.MoveToOverlappedChild (Application.Top));
  543. }
  544. // Invoke Tests
  545. // TODO: Test with threading scenarios
  546. [Fact]
  547. public void Invoke_Adds_Idle ()
  548. {
  549. Application.Init (new FakeDriver ());
  550. var top = new Toplevel ();
  551. var rs = Application.Begin (top);
  552. bool firstIteration = false;
  553. var actionCalled = 0;
  554. Application.Invoke (() => { actionCalled++; });
  555. Application.MainLoop.Running = true;
  556. Application.RunIteration (ref rs, ref firstIteration);
  557. Assert.Equal (1, actionCalled);
  558. Application.Shutdown ();
  559. }
  560. }