ApplicationTests.cs 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940
  1. #nullable enable
  2. using System.Diagnostics;
  3. using Xunit.Abstractions;
  4. using static Terminal.Gui.Configuration.ConfigurationManager;
  5. // Alias Console to MockConsole so we don't accidentally use Console
  6. namespace UnitTests.ApplicationTests;
  7. public class ApplicationTests
  8. {
  9. public ApplicationTests (ITestOutputHelper output)
  10. {
  11. _output = output;
  12. #if DEBUG_IDISPOSABLE
  13. View.EnableDebugIDisposableAsserts = true;
  14. View.Instances.Clear ();
  15. SessionToken.Instances.Clear ();
  16. #endif
  17. }
  18. private readonly ITestOutputHelper _output;
  19. [Fact]
  20. public void AddTimeout_Fires ()
  21. {
  22. IApplication app = ApplicationImpl.Instance; // Force legacy
  23. app.Init ("fake");
  24. uint timeoutTime = 100;
  25. var timeoutFired = false;
  26. // Setup a timeout that will fire
  27. app.AddTimeout (
  28. TimeSpan.FromMilliseconds (timeoutTime),
  29. () =>
  30. {
  31. timeoutFired = true;
  32. // Return false so the timer does not repeat
  33. return false;
  34. }
  35. );
  36. // The timeout has not fired yet
  37. Assert.False (timeoutFired);
  38. // Block the thread to prove the timeout does not fire on a background thread
  39. Thread.Sleep ((int)timeoutTime * 2);
  40. Assert.False (timeoutFired);
  41. // Phase 2: Ambiguous method call after Toplevel implements IRunnable - use non-generic Run()
  42. app.StopAfterFirstIteration = true;
  43. Toplevel top = new ();
  44. app.Run (top);
  45. top.Dispose ();
  46. // The timeout should have fired
  47. Assert.True (timeoutFired);
  48. app.Shutdown ();
  49. }
  50. [Fact]
  51. [SetupFakeApplication]
  52. public void Begin_Null_Toplevel_Throws ()
  53. {
  54. // Test null Toplevel
  55. Assert.Throws<ArgumentNullException> (() => Application.Begin (null!));
  56. }
  57. [Fact]
  58. [SetupFakeApplication]
  59. public void Begin_Sets_Application_Top_To_Console_Size ()
  60. {
  61. Assert.Null (Application.TopRunnable);
  62. Application.Driver!.SetScreenSize (80, 25);
  63. Toplevel top = new ();
  64. Application.Begin (top);
  65. Assert.Equal (new (0, 0, 80, 25), Application.TopRunnable!.Frame);
  66. Application.Driver!.SetScreenSize (5, 5);
  67. Assert.Equal (new (0, 0, 5, 5), Application.TopRunnable!.Frame);
  68. top.Dispose ();
  69. }
  70. [Fact]
  71. [SetupFakeApplication]
  72. public void End_And_Shutdown_Should_Not_Dispose_ApplicationTop ()
  73. {
  74. Assert.Null (Application.TopRunnable);
  75. SessionToken rs = Application.Begin (new ());
  76. Application.TopRunnable!.Title = "End_And_Shutdown_Should_Not_Dispose_ApplicationTop";
  77. Assert.Equal (rs.Toplevel, Application.TopRunnable);
  78. Application.End (rs);
  79. #if DEBUG_IDISPOSABLE
  80. Assert.True (rs.WasDisposed);
  81. Assert.False (Application.TopRunnable!.WasDisposed); // Is true because the rs.Toplevel is the same as Application.TopRunnable
  82. #endif
  83. Assert.Null (rs.Toplevel);
  84. Toplevel top = Application.TopRunnable;
  85. #if DEBUG_IDISPOSABLE
  86. Exception exception = Record.Exception (Application.Shutdown);
  87. Assert.NotNull (exception);
  88. Assert.False (top.WasDisposed);
  89. top.Dispose ();
  90. Assert.True (top.WasDisposed);
  91. #endif
  92. }
  93. [Fact]
  94. [SetupFakeApplication]
  95. public void Init_Begin_End_Cleans_Up ()
  96. {
  97. // Start stopwatch
  98. var stopwatch = new Stopwatch ();
  99. stopwatch.Start ();
  100. SessionToken? sessionToken = null;
  101. EventHandler<SessionTokenEventArgs> newSessionTokenFn = (s, e) =>
  102. {
  103. Assert.NotNull (e.State);
  104. sessionToken = e.State;
  105. };
  106. Application.SessionBegun += newSessionTokenFn;
  107. var topLevel = new Toplevel ();
  108. SessionToken rs = Application.Begin (topLevel);
  109. Assert.NotNull (rs);
  110. Assert.NotNull (sessionToken);
  111. Assert.Equal (rs, sessionToken);
  112. Assert.Equal (topLevel, Application.TopRunnable);
  113. Application.SessionBegun -= newSessionTokenFn;
  114. Application.End (sessionToken);
  115. Assert.NotNull (Application.TopRunnable);
  116. Assert.NotNull (Application.Driver);
  117. topLevel.Dispose ();
  118. // Stop stopwatch
  119. stopwatch.Stop ();
  120. _output.WriteLine ($"Load took {stopwatch.ElapsedMilliseconds} ms");
  121. }
  122. [Fact]
  123. public void Init_KeyBindings_Are_Not_Reset ()
  124. {
  125. Debug.Assert (!IsEnabled);
  126. try
  127. {
  128. // arrange
  129. ThrowOnJsonErrors = true;
  130. Application.QuitKey = Key.Q;
  131. Assert.Equal (Key.Q, Application.QuitKey);
  132. Application.Init ("fake");
  133. Assert.Equal (Key.Q, Application.QuitKey);
  134. }
  135. finally
  136. {
  137. Application.ResetState ();
  138. }
  139. }
  140. [Fact]
  141. public void Init_NoParam_ForceDriver_Works ()
  142. {
  143. Application.ForceDriver = "Fake";
  144. Application.Init ();
  145. Assert.Equal ("fake", Application.Driver!.GetName ());
  146. Application.ResetState ();
  147. }
  148. [Fact]
  149. public void Init_Null_Driver_Should_Pick_A_Driver ()
  150. {
  151. Application.Init ();
  152. Assert.NotNull (Application.Driver);
  153. Application.Shutdown ();
  154. }
  155. [Fact]
  156. public void Init_ResetState_Resets_Properties ()
  157. {
  158. ThrowOnJsonErrors = true;
  159. // For all the fields/properties of Application, check that they are reset to their default values
  160. // Set some values
  161. Application.Init (driverName: "fake");
  162. // Application.IsInitialized = true;
  163. // Reset
  164. Application.ResetState ();
  165. CheckReset ();
  166. // Set the values that can be set
  167. Application.Initialized = true;
  168. Application.MainThreadId = 1;
  169. //Application._topLevels = new List<Toplevel> ();
  170. Application.CachedViewsUnderMouse.Clear ();
  171. //Application.SupportedCultures = new List<CultureInfo> ();
  172. Application.Force16Colors = true;
  173. //Application.ForceDriver = "driver";
  174. Application.StopAfterFirstIteration = true;
  175. Application.PrevTabGroupKey = Key.A;
  176. Application.NextTabGroupKey = Key.B;
  177. Application.QuitKey = Key.C;
  178. Application.KeyBindings.Add (Key.D, Command.Cancel);
  179. Application.CachedViewsUnderMouse.Clear ();
  180. //Application.WantContinuousButtonPressedView = new View ();
  181. // Mouse
  182. Application.LastMousePosition = new Point (1, 1);
  183. Application.ResetState ();
  184. CheckReset ();
  185. ThrowOnJsonErrors = false;
  186. return;
  187. void CheckReset ()
  188. {
  189. // Check that all fields and properties are set to their default values
  190. // Public Properties
  191. Assert.Null (Application.TopRunnable);
  192. Assert.Null (Application.Mouse.MouseGrabView);
  193. // Don't check Application.ForceDriver
  194. // Assert.Empty (Application.ForceDriver);
  195. // Don't check Application.Force16Colors
  196. //Assert.False (Application.Force16Colors);
  197. Assert.Null (Application.Driver);
  198. Assert.False (Application.StopAfterFirstIteration);
  199. // Commented out because if CM changed the defaults, those changes should
  200. // persist across Inits.
  201. //Assert.Equal (Key.Tab.WithShift, Application.PrevTabKey);
  202. //Assert.Equal (Key.Tab, Application.NextTabKey);
  203. //Assert.Equal (Key.F6.WithShift, Application.PrevTabGroupKey);
  204. //Assert.Equal (Key.F6, Application.NextTabGroupKey);
  205. //Assert.Equal (Key.Esc, Application.QuitKey);
  206. // Internal properties
  207. Assert.False (Application.Initialized);
  208. Assert.Equal (Application.GetSupportedCultures (), Application.SupportedCultures);
  209. Assert.Equal (Application.GetAvailableCulturesFromEmbeddedResources (), Application.SupportedCultures);
  210. Assert.Null (Application.MainThreadId);
  211. Assert.Empty (Application.SessionStack);
  212. Assert.Empty (Application.CachedViewsUnderMouse);
  213. // Mouse
  214. // Do not reset _lastMousePosition
  215. //Assert.Null (Application._lastMousePosition);
  216. // Navigation
  217. // Assert.Null (Application.Navigation);
  218. // Popover
  219. //Assert.Null (Application.Popover);
  220. // Events - Can't check
  221. //Assert.Null (GetEventSubscribers (typeof (Application), "InitializedChanged"));
  222. //Assert.Null (GetEventSubscribers (typeof (Application), "SessionBegun"));
  223. //Assert.Null (GetEventSubscribers (typeof (Application), "Iteration"));
  224. //Assert.Null (GetEventSubscribers (typeof (Application), "ScreenChanged"));
  225. //Assert.Null (GetEventSubscribers (typeof (Application.Mouse), "MouseEvent"));
  226. //Assert.Null (GetEventSubscribers (typeof (Application.Keyboard), "KeyDown"));
  227. //Assert.Null (GetEventSubscribers (typeof (Application.Keyboard), "KeyUp"));
  228. }
  229. }
  230. [Fact]
  231. public void Init_Shutdown_Cleans_Up ()
  232. {
  233. // Verify initial state is per spec
  234. //Pre_Init_State ();
  235. Application.Init ("fake");
  236. // Verify post-Init state is correct
  237. //Post_Init_State ();
  238. Application.Shutdown ();
  239. // Verify state is back to initial
  240. //Pre_Init_State ();
  241. #if DEBUG_IDISPOSABLE
  242. // Validate there are no outstanding Responder-based instances
  243. // after a scenario was selected to run. This proves the main UI Catalog
  244. // 'app' closed cleanly.
  245. Assert.Empty (View.Instances);
  246. #endif
  247. }
  248. [Fact]
  249. public void Init_Shutdown_Fire_InitializedChanged ()
  250. {
  251. var initialized = false;
  252. var shutdown = false;
  253. Application.InitializedChanged += OnApplicationOnInitializedChanged;
  254. Application.Init (driverName: "fake");
  255. Assert.True (initialized);
  256. Assert.False (shutdown);
  257. Application.Shutdown ();
  258. Assert.True (initialized);
  259. Assert.True (shutdown);
  260. Application.InitializedChanged -= OnApplicationOnInitializedChanged;
  261. return;
  262. void OnApplicationOnInitializedChanged (object? s, EventArgs<bool> a)
  263. {
  264. if (a.Value)
  265. {
  266. initialized = true;
  267. }
  268. else
  269. {
  270. shutdown = true;
  271. }
  272. }
  273. }
  274. [Fact]
  275. [SetupFakeApplication]
  276. public void Init_Unbalanced_Throws ()
  277. {
  278. Assert.Throws<InvalidOperationException> (() =>
  279. Application.Init ("fake")
  280. );
  281. }
  282. [Fact]
  283. [SetupFakeApplication]
  284. public void Init_Unbalanced_Throws2 ()
  285. {
  286. // Now try the other way
  287. Assert.Throws<InvalidOperationException> (() => Application.Init ("fake"));
  288. }
  289. [Fact]
  290. public void Init_WithoutTopLevelFactory_Begin_End_Cleans_Up ()
  291. {
  292. Application.StopAfterFirstIteration = true;
  293. // NOTE: Run<T>, when called after Init has been called behaves differently than
  294. // when called if Init has not been called.
  295. Toplevel topLevel = new ();
  296. Application.Init ("fake");
  297. SessionToken? sessionToken = null;
  298. EventHandler<SessionTokenEventArgs> newSessionTokenFn = (s, e) =>
  299. {
  300. Assert.NotNull (e.State);
  301. sessionToken = e.State;
  302. };
  303. Application.SessionBegun += newSessionTokenFn;
  304. SessionToken rs = Application.Begin (topLevel);
  305. Assert.NotNull (rs);
  306. Assert.NotNull (sessionToken);
  307. Assert.Equal (rs, sessionToken);
  308. Assert.Equal (topLevel, Application.TopRunnable);
  309. Application.SessionBegun -= newSessionTokenFn;
  310. Application.End (sessionToken);
  311. Assert.NotNull (Application.TopRunnable);
  312. Assert.NotNull (Application.Driver);
  313. topLevel.Dispose ();
  314. Application.Shutdown ();
  315. Assert.Null (Application.TopRunnable);
  316. Assert.Null (Application.Driver);
  317. }
  318. [Fact]
  319. [SetupFakeApplication]
  320. public void Internal_Properties_Correct ()
  321. {
  322. Assert.True (Application.Initialized);
  323. Assert.Null (Application.TopRunnable);
  324. SessionToken rs = Application.Begin (new ());
  325. Assert.Equal (Application.TopRunnable, rs.Toplevel);
  326. Assert.Null (Application.Mouse.MouseGrabView); // public
  327. Application.TopRunnable!.Dispose ();
  328. }
  329. // Invoke Tests
  330. // TODO: Test with threading scenarios
  331. [Fact]
  332. [SetupFakeApplication]
  333. public void Invoke_Adds_Idle ()
  334. {
  335. Toplevel top = new ();
  336. SessionToken rs = Application.Begin (top);
  337. var actionCalled = 0;
  338. Application.Invoke ((_) => { actionCalled++; });
  339. Application.TimedEvents!.RunTimers ();
  340. Assert.Equal (1, actionCalled);
  341. top.Dispose ();
  342. }
  343. [Fact]
  344. public void Run_Iteration_Fires ()
  345. {
  346. var iteration = 0;
  347. Application.Init ("fake");
  348. Application.Iteration += Application_Iteration;
  349. Application.Run<Toplevel> ().Dispose ();
  350. Application.Iteration -= Application_Iteration;
  351. Assert.Equal (1, iteration);
  352. Application.Shutdown ();
  353. return;
  354. void Application_Iteration (object? sender, EventArgs<IApplication?> e)
  355. {
  356. if (iteration > 0)
  357. {
  358. Assert.Fail ();
  359. }
  360. iteration++;
  361. Application.RequestStop ();
  362. }
  363. }
  364. [Fact]
  365. [SetupFakeApplication]
  366. public void Screen_Size_Changes ()
  367. {
  368. IDriver? driver = Application.Driver;
  369. Application.Driver!.SetScreenSize (80, 25);
  370. Assert.Equal (new (0, 0, 80, 25), driver!.Screen);
  371. Assert.Equal (new (0, 0, 80, 25), Application.Screen);
  372. // TODO: Should not be possible to manually change these at whim!
  373. driver.Cols = 100;
  374. driver.Rows = 30;
  375. // IDriver.Screen isn't assignable
  376. //driver.Screen = new (0, 0, driver.Cols, Rows);
  377. Application.Driver!.SetScreenSize (100, 30);
  378. Assert.Equal (new (0, 0, 100, 30), driver.Screen);
  379. // Assert does not make sense
  380. // Assert.NotEqual (new (0, 0, 100, 30), Application.Screen);
  381. // Assert.Equal (new (0, 0, 80, 25), Application.Screen);
  382. Application.Screen = new (0, 0, driver.Cols, driver.Rows);
  383. Assert.Equal (new (0, 0, 100, 30), driver.Screen);
  384. }
  385. [Fact]
  386. public void Shutdown_Alone_Does_Nothing () { Application.Shutdown (); }
  387. //[Fact]
  388. //public void InitState_Throws_If_Driver_Is_Null ()
  389. //{
  390. // Assert.Throws<ArgumentNullException> (static () => Application.SubscribeDriverEvents ());
  391. //}
  392. #region RunTests
  393. [Fact]
  394. [SetupFakeApplication]
  395. public void Run_T_After_InitWithDriver_with_TopLevel_Does_Not_Throws ()
  396. {
  397. Application.StopAfterFirstIteration = true;
  398. // Run<Toplevel> when already initialized or not with a Driver will not throw (because Window is derived from Toplevel)
  399. // Using another type not derived from Toplevel will throws at compile time
  400. Application.Run<Window> ();
  401. Assert.True (Application.TopRunnable is Window);
  402. Application.TopRunnable!.Dispose ();
  403. }
  404. [Fact]
  405. public void Run_T_After_InitWithDriver_with_TopLevel_and_Driver_Does_Not_Throws ()
  406. {
  407. Application.StopAfterFirstIteration = true;
  408. // Run<Toplevel> when already initialized or not with a Driver will not throw (because Window is derived from Toplevel)
  409. // Using another type not derived from Toplevel will throws at compile time
  410. Application.Run<Window> (null, "fake");
  411. Assert.True (Application.TopRunnable is Window);
  412. Application.TopRunnable!.Dispose ();
  413. // Run<Toplevel> when already initialized or not with a Driver will not throw (because Dialog is derived from Toplevel)
  414. Application.Run<Dialog> (null, "fake");
  415. Assert.True (Application.TopRunnable is Dialog);
  416. Application.TopRunnable!.Dispose ();
  417. Application.Shutdown ();
  418. }
  419. [Fact]
  420. [SetupFakeApplication]
  421. public void Run_T_After_Init_Does_Not_Disposes_Application_Top ()
  422. {
  423. // Init doesn't create a Toplevel and assigned it to Application.TopRunnable
  424. // but Begin does
  425. var initTop = new Toplevel ();
  426. Application.Iteration += OnApplicationOnIteration;
  427. Application.Run<Toplevel> ();
  428. Application.Iteration -= OnApplicationOnIteration;
  429. #if DEBUG_IDISPOSABLE
  430. Assert.False (initTop.WasDisposed);
  431. initTop.Dispose ();
  432. Assert.True (initTop.WasDisposed);
  433. #endif
  434. Application.TopRunnable!.Dispose ();
  435. return;
  436. void OnApplicationOnIteration (object? s, EventArgs<IApplication?> a)
  437. {
  438. Assert.NotEqual (initTop, Application.TopRunnable);
  439. #if DEBUG_IDISPOSABLE
  440. Assert.False (initTop.WasDisposed);
  441. #endif
  442. Application.RequestStop ();
  443. }
  444. }
  445. [Fact]
  446. [SetupFakeApplication]
  447. public void Run_T_After_InitWithDriver_with_TestTopLevel_DoesNotThrow ()
  448. {
  449. Application.StopAfterFirstIteration = true;
  450. // Init has been called and we're passing no driver to Run<TestTopLevel>. This is ok.
  451. Application.Run<Toplevel> ();
  452. Application.TopRunnable!.Dispose ();
  453. }
  454. [Fact]
  455. [SetupFakeApplication]
  456. public void Run_T_After_InitNullDriver_with_TestTopLevel_DoesNotThrow ()
  457. {
  458. Application.StopAfterFirstIteration = true;
  459. // Init has been called, selecting FakeDriver; we're passing no driver to Run<TestTopLevel>. Should be fine.
  460. Application.Run<Toplevel> ();
  461. Application.TopRunnable!.Dispose ();
  462. }
  463. [Fact]
  464. [SetupFakeApplication]
  465. public void Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws ()
  466. {
  467. Application.Driver = null;
  468. // Init has been called, but Driver has been set to null. Bad.
  469. Assert.Throws<InvalidOperationException> (() => Application.Run<Toplevel> ());
  470. }
  471. [Fact]
  472. [SetupFakeApplication]
  473. public void Run_T_NoInit_DoesNotThrow ()
  474. {
  475. Application.StopAfterFirstIteration = true;
  476. Application.Run<Toplevel> ().Dispose ();
  477. }
  478. [Fact]
  479. [SetupFakeApplication]
  480. public void Run_T_NoInit_WithDriver_DoesNotThrow ()
  481. {
  482. Application.StopAfterFirstIteration = true;
  483. // Init has NOT been called and we're passing a valid driver to Run<TestTopLevel>. This is ok.
  484. Application.Run<Toplevel> (null, "fake");
  485. Application.TopRunnable!.Dispose ();
  486. }
  487. [Fact]
  488. [SetupFakeApplication]
  489. public void Run_RequestStop_Stops ()
  490. {
  491. var top = new Toplevel ();
  492. SessionToken rs = Application.Begin (top);
  493. Assert.NotNull (rs);
  494. Application.Iteration += OnApplicationOnIteration;
  495. Application.Run (top);
  496. Application.Iteration -= OnApplicationOnIteration;
  497. top.Dispose ();
  498. return;
  499. void OnApplicationOnIteration (object? s, EventArgs<IApplication?> a) { Application.RequestStop (); }
  500. }
  501. [Fact]
  502. [SetupFakeApplication]
  503. public void Run_Sets_Running_True ()
  504. {
  505. var top = new Toplevel ();
  506. SessionToken rs = Application.Begin (top);
  507. Assert.NotNull (rs);
  508. Application.Iteration += OnApplicationOnIteration;
  509. Application.Run (top);
  510. Application.Iteration -= OnApplicationOnIteration;
  511. top.Dispose ();
  512. return;
  513. void OnApplicationOnIteration (object? s, EventArgs<IApplication?> a)
  514. {
  515. Assert.True (top.Running);
  516. top.RequestStop ();
  517. }
  518. }
  519. [Fact]
  520. [SetupFakeApplication]
  521. public void Run_RunningFalse_Stops ()
  522. {
  523. var top = new Toplevel ();
  524. SessionToken rs = Application.Begin (top);
  525. Assert.NotNull (rs);
  526. Application.Iteration += OnApplicationOnIteration;
  527. Application.Run (top);
  528. Application.Iteration -= OnApplicationOnIteration;
  529. top.Dispose ();
  530. return;
  531. void OnApplicationOnIteration (object? s, EventArgs<IApplication?> a) { top.Running = false; }
  532. }
  533. [Fact]
  534. [SetupFakeApplication]
  535. public void Run_Loaded_Ready_Unloaded_Events ()
  536. {
  537. Application.StopAfterFirstIteration = true;
  538. Toplevel top = new ();
  539. var count = 0;
  540. top.Loaded += (s, e) => count++;
  541. top.Ready += (s, e) => count++;
  542. top.Unloaded += (s, e) => count++;
  543. Application.Run (top);
  544. top.Dispose ();
  545. }
  546. // TODO: All Toplevel layout tests should be moved to ToplevelTests.cs
  547. [Fact]
  548. [SetupFakeApplication]
  549. public void Run_A_Modal_Toplevel_Refresh_Background_On_Moving ()
  550. {
  551. // Don't use Dialog here as it has more layout logic. Use Window instead.
  552. var w = new Window
  553. {
  554. Width = 5, Height = 5,
  555. Arrangement = ViewArrangement.Movable
  556. };
  557. Application.Driver!.SetScreenSize (10, 10);
  558. SessionToken rs = Application.Begin (w);
  559. // Don't use visuals to test as style of border can change over time.
  560. Assert.Equal (new (0, 0), w.Frame.Location);
  561. Application.RaiseMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
  562. Assert.Equal (w.Border, Application.Mouse.MouseGrabView);
  563. Assert.Equal (new (0, 0), w.Frame.Location);
  564. // Move down and to the right.
  565. Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition });
  566. Assert.Equal (new (1, 1), w.Frame.Location);
  567. Application.End (rs);
  568. w.Dispose ();
  569. }
  570. [Fact]
  571. [SetupFakeApplication]
  572. public void End_Does_Not_Dispose ()
  573. {
  574. var top = new Toplevel ();
  575. Window w = new ();
  576. Application.StopAfterFirstIteration = true;
  577. Application.Run (w);
  578. #if DEBUG_IDISPOSABLE
  579. Assert.False (w.WasDisposed);
  580. #endif
  581. Assert.NotNull (w);
  582. Assert.Equal (string.Empty, w.Title); // Valid - w has not been disposed. The user may want to run it again
  583. Assert.NotNull (Application.TopRunnable);
  584. Assert.Equal (w, Application.TopRunnable);
  585. Assert.NotEqual (top, Application.TopRunnable);
  586. Application.Run (w); // Valid - w has not been disposed.
  587. #if DEBUG_IDISPOSABLE
  588. Assert.False (w.WasDisposed);
  589. Exception exception = Record.Exception (Application.Shutdown); // Invalid - w has not been disposed.
  590. Assert.NotNull (exception);
  591. w.Dispose ();
  592. Assert.True (w.WasDisposed);
  593. //exception = Record.Exception (
  594. // () => Application.Run (
  595. // w)); // Invalid - w has not been disposed. Run it in debug mode will throw, otherwise the user may want to run it again
  596. //Assert.NotNull (exception);
  597. // TODO: Re-enable this when we are done debug logging of ctx.Source.Title in RaiseSelecting
  598. //exception = Record.Exception (() => Assert.Equal (string.Empty, w.Title)); // Invalid - w has been disposed and cannot be accessed
  599. //Assert.NotNull (exception);
  600. //exception = Record.Exception (() => w.Title = "NewTitle"); // Invalid - w has been disposed and cannot be accessed
  601. //Assert.NotNull (exception);
  602. #endif
  603. }
  604. [Fact]
  605. public void Run_Creates_Top_Without_Init ()
  606. {
  607. Assert.Null (Application.TopRunnable);
  608. Application.StopAfterFirstIteration = true;
  609. Application.Iteration += OnApplicationOnIteration;
  610. Toplevel top = Application.Run (null, "fake");
  611. Application.Iteration -= OnApplicationOnIteration;
  612. #if DEBUG_IDISPOSABLE
  613. Assert.Equal (top, Application.TopRunnable);
  614. Assert.False (top.WasDisposed);
  615. Exception exception = Record.Exception (Application.Shutdown);
  616. Assert.NotNull (exception);
  617. Assert.False (top.WasDisposed);
  618. #endif
  619. // It's up to caller to dispose it
  620. top.Dispose ();
  621. #if DEBUG_IDISPOSABLE
  622. Assert.True (top.WasDisposed);
  623. #endif
  624. Assert.NotNull (Application.TopRunnable);
  625. Application.Shutdown ();
  626. Assert.Null (Application.TopRunnable);
  627. return;
  628. void OnApplicationOnIteration (object? s, EventArgs<IApplication?> e) { Assert.NotNull (Application.TopRunnable); }
  629. }
  630. [Fact]
  631. public void Run_T_Creates_Top_Without_Init ()
  632. {
  633. Assert.Null (Application.TopRunnable);
  634. Application.StopAfterFirstIteration = true;
  635. Application.Run<Toplevel> (null, "fake");
  636. #if DEBUG_IDISPOSABLE
  637. Assert.False (Application.TopRunnable!.WasDisposed);
  638. Exception exception = Record.Exception (Application.Shutdown);
  639. Assert.NotNull (exception);
  640. Assert.False (Application.TopRunnable!.WasDisposed);
  641. // It's up to caller to dispose it
  642. Application.TopRunnable!.Dispose ();
  643. Assert.True (Application.TopRunnable!.WasDisposed);
  644. #endif
  645. Assert.NotNull (Application.TopRunnable);
  646. Application.Shutdown ();
  647. Assert.Null (Application.TopRunnable);
  648. }
  649. [Fact]
  650. public void Run_t_Does_Not_Creates_Top_Without_Init ()
  651. {
  652. // When a Toplevel is created it must already have all the Application configuration loaded
  653. // This is only possible by two ways:
  654. // 1 - Using Application.Init first
  655. // 2 - Using Application.Run() or Application.Run<T>()
  656. // The Application.Run(new(Toplevel)) must always call Application.Init() first because
  657. // the new(Toplevel) may be a derived class that is possible using Application static
  658. // properties that is only available after the Application.Init was called
  659. Assert.Null (Application.TopRunnable);
  660. Assert.Throws<NotInitializedException> (() => Application.Run (new Toplevel ()));
  661. Application.Init ("fake");
  662. Application.Iteration += OnApplication_OnIteration;
  663. Application.Run (new Toplevel ());
  664. Application.Iteration -= OnApplication_OnIteration;
  665. #if DEBUG_IDISPOSABLE
  666. Assert.False (Application.TopRunnable!.WasDisposed);
  667. Exception exception = Record.Exception (Application.Shutdown);
  668. Assert.NotNull (exception);
  669. Assert.False (Application.TopRunnable!.WasDisposed);
  670. // It's up to caller to dispose it
  671. Application.TopRunnable!.Dispose ();
  672. Assert.True (Application.TopRunnable!.WasDisposed);
  673. #endif
  674. Assert.NotNull (Application.TopRunnable);
  675. Application.Shutdown ();
  676. Assert.Null (Application.TopRunnable);
  677. return;
  678. void OnApplication_OnIteration (object? s, EventArgs<IApplication?> e)
  679. {
  680. Assert.NotNull (Application.TopRunnable);
  681. Application.RequestStop ();
  682. }
  683. }
  684. private class TestToplevel : Toplevel
  685. { }
  686. [Fact]
  687. public void Run_T_With_V2_Driver_Does_Not_Call_ResetState_After_Init ()
  688. {
  689. Assert.False (Application.Initialized);
  690. Application.Init ("fake");
  691. Assert.True (Application.Initialized);
  692. Task.Run (() => { Task.Delay (300).Wait (); })
  693. .ContinueWith (
  694. (t, _) =>
  695. {
  696. // no longer loading
  697. Application.Invoke ((app) => { app.RequestStop (); });
  698. },
  699. TaskScheduler.FromCurrentSynchronizationContext ());
  700. Application.Run<TestToplevel> ();
  701. Assert.NotNull (Application.Driver);
  702. Assert.NotNull (Application.TopRunnable);
  703. Assert.False (Application.TopRunnable!.Running);
  704. Application.TopRunnable!.Dispose ();
  705. Application.Shutdown ();
  706. }
  707. // TODO: Add tests for Run that test errorHandler
  708. #endregion
  709. #region ShutdownTests
  710. [Fact]
  711. public async Task Shutdown_Allows_Async ()
  712. {
  713. var isCompletedSuccessfully = false;
  714. async Task TaskWithAsyncContinuation ()
  715. {
  716. await Task.Yield ();
  717. await Task.Yield ();
  718. isCompletedSuccessfully = true;
  719. }
  720. Application.Shutdown ();
  721. Assert.False (isCompletedSuccessfully);
  722. await TaskWithAsyncContinuation ();
  723. Thread.Sleep (100);
  724. Assert.True (isCompletedSuccessfully);
  725. }
  726. [Fact]
  727. public void Shutdown_Resets_SyncContext ()
  728. {
  729. Application.Shutdown ();
  730. Assert.Null (SynchronizationContext.Current);
  731. }
  732. #endregion
  733. }