ApplicationTests.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128
  1. using System.Diagnostics;
  2. using UnitTests;
  3. using Xunit.Abstractions;
  4. using static Terminal.Gui.ConfigurationManager;
  5. // Alias Console to MockConsole so we don't accidentally use Console
  6. namespace Terminal.Gui.ApplicationTests;
  7. public class ApplicationTests
  8. {
  9. public ApplicationTests (ITestOutputHelper output)
  10. {
  11. _output = output;
  12. ConsoleDriver.RunningUnitTests = true;
  13. Locations = ConfigLocations.Default;
  14. #if DEBUG_IDISPOSABLE
  15. View.DebugIDisposable = true;
  16. View.Instances.Clear ();
  17. RunState.Instances.Clear ();
  18. #endif
  19. }
  20. private readonly ITestOutputHelper _output;
  21. private object _timeoutLock;
  22. [Fact]
  23. public void AddTimeout_Fires ()
  24. {
  25. Assert.Null (_timeoutLock);
  26. _timeoutLock = new ();
  27. uint timeoutTime = 250;
  28. var initialized = false;
  29. var iteration = 0;
  30. var shutdown = false;
  31. object timeout = null;
  32. var timeoutCount = 0;
  33. Application.InitializedChanged += OnApplicationOnInitializedChanged;
  34. Application.Init (new FakeDriver ());
  35. Assert.True (initialized);
  36. Assert.False (shutdown);
  37. _output.WriteLine ("Application.Run<Toplevel> ().Dispose ()..");
  38. Application.Run<Toplevel> ().Dispose ();
  39. _output.WriteLine ("Back from Application.Run<Toplevel> ().Dispose ()");
  40. Assert.True (initialized);
  41. Assert.False (shutdown);
  42. Assert.Equal (1, timeoutCount);
  43. Application.Shutdown ();
  44. Application.InitializedChanged -= OnApplicationOnInitializedChanged;
  45. lock (_timeoutLock)
  46. {
  47. if (timeout is { })
  48. {
  49. Application.RemoveTimeout (timeout);
  50. timeout = null;
  51. }
  52. }
  53. Assert.True (initialized);
  54. Assert.True (shutdown);
  55. #if DEBUG_IDISPOSABLE
  56. Assert.Empty (View.Instances);
  57. #endif
  58. lock (_timeoutLock)
  59. {
  60. _timeoutLock = null;
  61. }
  62. return;
  63. void OnApplicationOnInitializedChanged (object s, EventArgs<bool> a)
  64. {
  65. if (a.CurrentValue)
  66. {
  67. Application.Iteration += OnApplicationOnIteration;
  68. initialized = true;
  69. lock (_timeoutLock)
  70. {
  71. _output.WriteLine ($"Setting timeout for {timeoutTime}ms");
  72. timeout = Application.AddTimeout (TimeSpan.FromMilliseconds (timeoutTime), TimeoutCallback);
  73. }
  74. }
  75. else
  76. {
  77. Application.Iteration -= OnApplicationOnIteration;
  78. shutdown = true;
  79. }
  80. }
  81. bool TimeoutCallback ()
  82. {
  83. lock (_timeoutLock)
  84. {
  85. _output.WriteLine ($"TimeoutCallback. Count: {++timeoutCount}. Application Iteration: {iteration}");
  86. if (timeout is { })
  87. {
  88. _output.WriteLine (" Nulling timeout.");
  89. timeout = null;
  90. }
  91. }
  92. // False means "don't re-do timer and remove it"
  93. return false;
  94. }
  95. void OnApplicationOnIteration (object s, IterationEventArgs a)
  96. {
  97. lock (_timeoutLock)
  98. {
  99. if (timeoutCount > 0)
  100. {
  101. _output.WriteLine ($"Iteration #{iteration} - Timeout fired. Calling Application.RequestStop.");
  102. Application.RequestStop ();
  103. return;
  104. }
  105. }
  106. iteration++;
  107. // Simulate a delay
  108. Thread.Sleep ((int)timeoutTime / 10);
  109. // Worst case scenario - something went wrong
  110. if (Application.Initialized && iteration > 25)
  111. {
  112. _output.WriteLine ($"Too many iterations ({iteration}): Calling Application.RequestStop.");
  113. Application.RequestStop ();
  114. }
  115. }
  116. }
  117. [Fact]
  118. public void Begin_Null_Toplevel_Throws ()
  119. {
  120. // Setup Mock driver
  121. Init ();
  122. // Test null Toplevel
  123. Assert.Throws<ArgumentNullException> (() => Application.Begin (null));
  124. Shutdown ();
  125. Assert.Null (Application.Top);
  126. Assert.Null (Application.MainLoop);
  127. Assert.Null (Application.Driver);
  128. }
  129. [Fact]
  130. [AutoInitShutdown (verifyShutdown: true)]
  131. public void Begin_Sets_Application_Top_To_Console_Size ()
  132. {
  133. Assert.Null (Application.Top);
  134. Toplevel top = new ();
  135. Application.Begin (top);
  136. Assert.Equal (new (0, 0, 80, 25), Application.Top.Frame);
  137. ((FakeDriver)Application.Driver!).SetBufferSize (5, 5);
  138. Assert.Equal (new (0, 0, 5, 5), Application.Top.Frame);
  139. top.Dispose ();
  140. }
  141. [Fact]
  142. public void End_And_Shutdown_Should_Not_Dispose_ApplicationTop ()
  143. {
  144. Init ();
  145. RunState rs = Application.Begin (new ());
  146. Assert.Equal (rs.Toplevel, Application.Top);
  147. Application.End (rs);
  148. #if DEBUG_IDISPOSABLE
  149. Assert.True (rs.WasDisposed);
  150. Assert.False (Application.Top.WasDisposed); // Is true because the rs.Toplevel is the same as Application.Top
  151. #endif
  152. Assert.Null (rs.Toplevel);
  153. Toplevel top = Application.Top;
  154. #if DEBUG_IDISPOSABLE
  155. Exception exception = Record.Exception (() => Shutdown ());
  156. Assert.NotNull (exception);
  157. Assert.False (top.WasDisposed);
  158. top.Dispose ();
  159. Assert.True (top.WasDisposed);
  160. #endif
  161. Shutdown ();
  162. Assert.Null (Application.Top);
  163. }
  164. [Fact]
  165. public void Init_Begin_End_Cleans_Up ()
  166. {
  167. // Start stopwatch
  168. Stopwatch stopwatch = new Stopwatch ();
  169. stopwatch.Start ();
  170. Init ();
  171. // Begin will cause Run() to be called, which will call Begin(). Thus will block the tests
  172. // if we don't stop
  173. Application.Iteration += (s, a) => { Application.RequestStop (); };
  174. RunState runstate = null;
  175. EventHandler<RunStateEventArgs> newRunStateFn = (s, e) =>
  176. {
  177. Assert.NotNull (e.State);
  178. runstate = e.State;
  179. };
  180. Application.NotifyNewRunState += newRunStateFn;
  181. var topLevel = new Toplevel ();
  182. RunState rs = Application.Begin (topLevel);
  183. Assert.NotNull (rs);
  184. Assert.NotNull (runstate);
  185. Assert.Equal (rs, runstate);
  186. Assert.Equal (topLevel, Application.Top);
  187. Application.NotifyNewRunState -= newRunStateFn;
  188. Application.End (runstate);
  189. Assert.NotNull (Application.Top);
  190. Assert.NotNull (Application.MainLoop);
  191. Assert.NotNull (Application.Driver);
  192. topLevel.Dispose ();
  193. Shutdown ();
  194. Assert.Null (Application.Top);
  195. Assert.Null (Application.MainLoop);
  196. Assert.Null (Application.Driver);
  197. // Stop stopwatch
  198. stopwatch.Stop ();
  199. _output.WriteLine ($"Load took {stopwatch.ElapsedMilliseconds} ms");
  200. }
  201. [Theory]
  202. [InlineData (typeof (FakeDriver))]
  203. [InlineData (typeof (NetDriver))]
  204. //[InlineData (typeof (ANSIDriver))]
  205. [InlineData (typeof (WindowsDriver))]
  206. [InlineData (typeof (CursesDriver))]
  207. public void Init_DriverName_Should_Pick_Correct_Driver (Type driverType)
  208. {
  209. var driver = (IConsoleDriver)Activator.CreateInstance (driverType);
  210. Application.Init (driverName: driverType.Name);
  211. Assert.NotNull (Application.Driver);
  212. Assert.NotEqual (driver, Application.Driver);
  213. Assert.Equal (driverType, Application.Driver?.GetType ());
  214. Shutdown ();
  215. }
  216. [Fact]
  217. public void Init_Null_Driver_Should_Pick_A_Driver ()
  218. {
  219. Application.Init ();
  220. Assert.NotNull (Application.Driver);
  221. Shutdown ();
  222. }
  223. [Theory]
  224. [InlineData (typeof (FakeDriver))]
  225. [InlineData (typeof (NetDriver))]
  226. [InlineData (typeof (WindowsDriver))]
  227. [InlineData (typeof (CursesDriver))]
  228. public void Init_ResetState_Resets_Properties (Type driverType)
  229. {
  230. ThrowOnJsonErrors = true;
  231. // For all the fields/properties of Application, check that they are reset to their default values
  232. // Set some values
  233. Application.Init (driverName: driverType.Name);
  234. // Application.IsInitialized = true;
  235. // Reset
  236. Application.ResetState ();
  237. void CheckReset ()
  238. {
  239. // Check that all fields and properties are set to their default values
  240. // Public Properties
  241. Assert.Null (Application.Top);
  242. Assert.Null (Application.MouseGrabView);
  243. Assert.Null (Application.WantContinuousButtonPressedView);
  244. // Don't check Application.ForceDriver
  245. // Assert.Empty (Application.ForceDriver);
  246. // Don't check Application.Force16Colors
  247. //Assert.False (Application.Force16Colors);
  248. Assert.Null (Application.Driver);
  249. Assert.Null (Application.MainLoop);
  250. Assert.False (Application.EndAfterFirstIteration);
  251. Assert.Equal (Key.Tab.WithShift, Application.PrevTabKey);
  252. Assert.Equal (Key.Tab, Application.NextTabKey);
  253. Assert.Equal (Key.F6.WithShift, Application.PrevTabGroupKey);
  254. Assert.Equal (Key.F6, Application.NextTabGroupKey);
  255. Assert.Equal (Key.Esc, Application.QuitKey);
  256. // Internal properties
  257. Assert.False (Application.Initialized);
  258. Assert.Equal (Application.GetSupportedCultures (), Application.SupportedCultures);
  259. Assert.Equal (Application.GetAvailableCulturesFromEmbeddedResources (), Application.SupportedCultures);
  260. Assert.False (Application._forceFakeConsole);
  261. Assert.Equal (-1, Application.MainThreadId);
  262. Assert.Empty (Application.TopLevels);
  263. Assert.Empty (Application._cachedViewsUnderMouse);
  264. // Mouse
  265. Assert.Null (Application._lastMousePosition);
  266. // Navigation
  267. Assert.Null (Application.Navigation);
  268. // Events - Can't check
  269. //Assert.Null (Application.NotifyNewRunState);
  270. //Assert.Null (Application.NotifyNewRunState);
  271. //Assert.Null (Application.Iteration);
  272. //Assert.Null (Application.SizeChanging);
  273. //Assert.Null (Application.GrabbedMouse);
  274. //Assert.Null (Application.UnGrabbingMouse);
  275. //Assert.Null (Application.GrabbedMouse);
  276. //Assert.Null (Application.UnGrabbedMouse);
  277. //Assert.Null (Application.MouseEvent);
  278. //Assert.Null (Application.KeyDown);
  279. //Assert.Null (Application.KeyUp);
  280. }
  281. CheckReset ();
  282. // Set the values that can be set
  283. Application.Initialized = true;
  284. Application._forceFakeConsole = true;
  285. Application.MainThreadId = 1;
  286. //Application._topLevels = new List<Toplevel> ();
  287. Application._cachedViewsUnderMouse.Clear ();
  288. //Application.SupportedCultures = new List<CultureInfo> ();
  289. Application.Force16Colors = true;
  290. //Application.ForceDriver = "driver";
  291. Application.EndAfterFirstIteration = true;
  292. Application.PrevTabGroupKey = Key.A;
  293. Application.NextTabGroupKey = Key.B;
  294. Application.QuitKey = Key.C;
  295. Application.KeyBindings.Add (Key.D, Command.Cancel);
  296. Application._cachedViewsUnderMouse.Clear ();
  297. //Application.WantContinuousButtonPressedView = new View ();
  298. // Mouse
  299. Application._lastMousePosition = new Point (1, 1);
  300. Application.Navigation = new ();
  301. Application.ResetState ();
  302. CheckReset ();
  303. ThrowOnJsonErrors = false;
  304. }
  305. [Fact]
  306. public void Init_Shutdown_Cleans_Up ()
  307. {
  308. // Verify initial state is per spec
  309. //Pre_Init_State ();
  310. Application.Init (new FakeDriver ());
  311. // Verify post-Init state is correct
  312. //Post_Init_State ();
  313. Application.Shutdown ();
  314. // Verify state is back to initial
  315. //Pre_Init_State ();
  316. #if DEBUG_IDISPOSABLE
  317. // Validate there are no outstanding Responder-based instances
  318. // after a scenario was selected to run. This proves the main UI Catalog
  319. // 'app' closed cleanly.
  320. Assert.Empty (View.Instances);
  321. #endif
  322. }
  323. [Fact]
  324. public void Shutdown_Alone_Does_Nothing () { Application.Shutdown (); }
  325. [Theory]
  326. [InlineData (typeof (FakeDriver))]
  327. [InlineData (typeof (NetDriver))]
  328. [InlineData (typeof (WindowsDriver))]
  329. [InlineData (typeof (CursesDriver))]
  330. public void Init_Shutdown_Fire_InitializedChanged (Type driverType)
  331. {
  332. var initialized = false;
  333. var shutdown = false;
  334. Application.InitializedChanged += OnApplicationOnInitializedChanged;
  335. Application.Init (driverName: driverType.Name);
  336. Assert.True (initialized);
  337. Assert.False (shutdown);
  338. Application.Shutdown ();
  339. Assert.True (initialized);
  340. Assert.True (shutdown);
  341. Application.InitializedChanged -= OnApplicationOnInitializedChanged;
  342. return;
  343. void OnApplicationOnInitializedChanged (object s, EventArgs<bool> a)
  344. {
  345. if (a.CurrentValue)
  346. {
  347. initialized = true;
  348. }
  349. else
  350. {
  351. shutdown = true;
  352. }
  353. }
  354. }
  355. [Fact]
  356. public void Init_Unbalanced_Throws ()
  357. {
  358. Application.Init (new FakeDriver ());
  359. Assert.Throws<InvalidOperationException> (
  360. () =>
  361. Application.InternalInit (
  362. new FakeDriver ()
  363. )
  364. );
  365. Shutdown ();
  366. Assert.Null (Application.Top);
  367. Assert.Null (Application.MainLoop);
  368. Assert.Null (Application.Driver);
  369. // Now try the other way
  370. Application.InternalInit (new FakeDriver ());
  371. Assert.Throws<InvalidOperationException> (() => Application.Init (new FakeDriver ()));
  372. Shutdown ();
  373. Assert.Null (Application.Top);
  374. Assert.Null (Application.MainLoop);
  375. Assert.Null (Application.Driver);
  376. }
  377. [Fact]
  378. public void Init_WithoutTopLevelFactory_Begin_End_Cleans_Up ()
  379. {
  380. // Begin will cause Run() to be called, which will call Begin(). Thus will block the tests
  381. // if we don't stop
  382. Application.Iteration += (s, a) => { Application.RequestStop (); };
  383. // NOTE: Run<T>, when called after Init has been called behaves differently than
  384. // when called if Init has not been called.
  385. Toplevel topLevel = new ();
  386. Application.InternalInit (new FakeDriver ());
  387. RunState runstate = null;
  388. EventHandler<RunStateEventArgs> newRunStateFn = (s, e) =>
  389. {
  390. Assert.NotNull (e.State);
  391. runstate = e.State;
  392. };
  393. Application.NotifyNewRunState += newRunStateFn;
  394. RunState rs = Application.Begin (topLevel);
  395. Assert.NotNull (rs);
  396. Assert.NotNull (runstate);
  397. Assert.Equal (rs, runstate);
  398. Assert.Equal (topLevel, Application.Top);
  399. Application.NotifyNewRunState -= newRunStateFn;
  400. Application.End (runstate);
  401. Assert.NotNull (Application.Top);
  402. Assert.NotNull (Application.MainLoop);
  403. Assert.NotNull (Application.Driver);
  404. topLevel.Dispose ();
  405. Shutdown ();
  406. Assert.Null (Application.Top);
  407. Assert.Null (Application.MainLoop);
  408. Assert.Null (Application.Driver);
  409. }
  410. [Fact]
  411. public void Init_NoParam_ForceDriver_Works ()
  412. {
  413. Application.ForceDriver = "FakeDriver";
  414. Application.Init ();
  415. Assert.IsType<FakeDriver> (Application.Driver);
  416. Application.ResetState ();
  417. }
  418. [Fact]
  419. public void Init_KeyBindings_Set_To_Defaults ()
  420. {
  421. // arrange
  422. Locations = ConfigLocations.All;
  423. ThrowOnJsonErrors = true;
  424. Application.QuitKey = Key.Q;
  425. Application.Init (new FakeDriver ());
  426. Assert.Equal (Key.Esc, Application.QuitKey);
  427. Application.Shutdown ();
  428. }
  429. [Fact]
  430. public void Init_KeyBindings_Set_To_Custom ()
  431. {
  432. // arrange
  433. Locations = ConfigLocations.Runtime;
  434. ThrowOnJsonErrors = true;
  435. RuntimeConfig = """
  436. {
  437. "Application.QuitKey": "Ctrl-Q"
  438. }
  439. """;
  440. Assert.Equal (Key.Esc, Application.QuitKey);
  441. // Act
  442. Application.Init (new FakeDriver ());
  443. Assert.Equal (Key.Q.WithCtrl, Application.QuitKey);
  444. Assert.True (Application.KeyBindings.TryGet (Key.Q.WithCtrl, out _));
  445. Application.Shutdown ();
  446. Locations = ConfigLocations.Default;
  447. }
  448. [Fact]
  449. [AutoInitShutdown (verifyShutdown: true)]
  450. public void Internal_Properties_Correct ()
  451. {
  452. Assert.True (Application.Initialized);
  453. Assert.Null (Application.Top);
  454. RunState rs = Application.Begin (new ());
  455. Assert.Equal (Application.Top, rs.Toplevel);
  456. Assert.Null (Application.MouseGrabView); // public
  457. Assert.Null (Application.WantContinuousButtonPressedView); // public
  458. Application.Top.Dispose ();
  459. }
  460. // Invoke Tests
  461. // TODO: Test with threading scenarios
  462. [Fact]
  463. public void Invoke_Adds_Idle ()
  464. {
  465. Application.Init (new FakeDriver ());
  466. var top = new Toplevel ();
  467. RunState rs = Application.Begin (top);
  468. var firstIteration = false;
  469. var actionCalled = 0;
  470. Application.Invoke (() => { actionCalled++; });
  471. Application.MainLoop.Running = true;
  472. Application.RunIteration (ref rs, firstIteration);
  473. Assert.Equal (1, actionCalled);
  474. top.Dispose ();
  475. Application.Shutdown ();
  476. }
  477. [Fact]
  478. public void Run_Iteration_Fires ()
  479. {
  480. var iteration = 0;
  481. Application.Init (new FakeDriver ());
  482. Application.Iteration += Application_Iteration;
  483. Application.Run<Toplevel> ().Dispose ();
  484. Assert.Equal (1, iteration);
  485. Application.Shutdown ();
  486. return;
  487. void Application_Iteration (object sender, IterationEventArgs e)
  488. {
  489. if (iteration > 0)
  490. {
  491. Assert.Fail ();
  492. }
  493. iteration++;
  494. Application.RequestStop ();
  495. }
  496. }
  497. [Fact]
  498. public void Screen_Size_Changes ()
  499. {
  500. var driver = new FakeDriver ();
  501. Application.Init (driver);
  502. Assert.Equal (new (0, 0, 80, 25), driver.Screen);
  503. Assert.Equal (new (0, 0, 80, 25), Application.Screen);
  504. driver.Cols = 100;
  505. driver.Rows = 30;
  506. // IConsoleDriver.Screen isn't assignable
  507. //driver.Screen = new (0, 0, driver.Cols, Rows);
  508. Assert.Equal (new (0, 0, 100, 30), driver.Screen);
  509. Assert.NotEqual (new (0, 0, 100, 30), Application.Screen);
  510. Assert.Equal (new (0, 0, 80, 25), Application.Screen);
  511. Application.Screen = new (0, 0, driver.Cols, driver.Rows);
  512. Assert.Equal (new (0, 0, 100, 30), driver.Screen);
  513. Application.Shutdown ();
  514. }
  515. [Fact]
  516. public void InitState_Throws_If_Driver_Is_Null ()
  517. {
  518. Assert.Throws<ArgumentNullException> (static () => Application.SubscribeDriverEvents ());
  519. }
  520. private void Init ()
  521. {
  522. Application.Init (new FakeDriver ());
  523. Assert.NotNull (Application.Driver);
  524. Assert.NotNull (Application.MainLoop);
  525. Assert.NotNull (SynchronizationContext.Current);
  526. }
  527. private void Shutdown () { Application.Shutdown (); }
  528. #region RunTests
  529. [Fact]
  530. public void Run_T_After_InitWithDriver_with_TopLevel_Does_Not_Throws ()
  531. {
  532. // Setup Mock driver
  533. Init ();
  534. Application.Iteration += (s, e) => Application.RequestStop ();
  535. // Run<Toplevel> when already initialized or not with a Driver will not throw (because Window is derived from Toplevel)
  536. // Using another type not derived from Toplevel will throws at compile time
  537. Application.Run<Window> ();
  538. Assert.True (Application.Top is Window);
  539. Application.Top.Dispose ();
  540. Shutdown ();
  541. Assert.Null (Application.Top);
  542. Assert.Null (Application.MainLoop);
  543. Assert.Null (Application.Driver);
  544. }
  545. [Fact]
  546. public void Run_T_After_InitWithDriver_with_TopLevel_and_Driver_Does_Not_Throws ()
  547. {
  548. // Setup Mock driver
  549. Init ();
  550. Application.Iteration += (s, e) => Application.RequestStop ();
  551. // Run<Toplevel> when already initialized or not with a Driver will not throw (because Window is derived from Toplevel)
  552. // Using another type not derived from Toplevel will throws at compile time
  553. Application.Run<Window> (null, new FakeDriver ());
  554. Assert.True (Application.Top is Window);
  555. Application.Top.Dispose ();
  556. // Run<Toplevel> when already initialized or not with a Driver will not throw (because Dialog is derived from Toplevel)
  557. Application.Run<Dialog> (null, new FakeDriver ());
  558. Assert.True (Application.Top is Dialog);
  559. Application.Top.Dispose ();
  560. Shutdown ();
  561. Assert.Null (Application.Top);
  562. Assert.Null (Application.MainLoop);
  563. Assert.Null (Application.Driver);
  564. }
  565. [Fact]
  566. [TestRespondersDisposed]
  567. public void Run_T_After_Init_Does_Not_Disposes_Application_Top ()
  568. {
  569. Init ();
  570. // Init doesn't create a Toplevel and assigned it to Application.Top
  571. // but Begin does
  572. var initTop = new Toplevel ();
  573. Application.Iteration += (s, a) =>
  574. {
  575. Assert.NotEqual (initTop, Application.Top);
  576. #if DEBUG_IDISPOSABLE
  577. Assert.False (initTop.WasDisposed);
  578. #endif
  579. Application.RequestStop ();
  580. };
  581. Application.Run<Toplevel> ();
  582. #if DEBUG_IDISPOSABLE
  583. Assert.False (initTop.WasDisposed);
  584. initTop.Dispose ();
  585. Assert.True (initTop.WasDisposed);
  586. #endif
  587. Application.Top.Dispose ();
  588. Shutdown ();
  589. Assert.Null (Application.Top);
  590. Assert.Null (Application.MainLoop);
  591. Assert.Null (Application.Driver);
  592. }
  593. [Fact]
  594. [TestRespondersDisposed]
  595. public void Run_T_After_InitWithDriver_with_TestTopLevel_DoesNotThrow ()
  596. {
  597. // Setup Mock driver
  598. Init ();
  599. Application.Iteration += (s, a) => { Application.RequestStop (); };
  600. // Init has been called and we're passing no driver to Run<TestTopLevel>. This is ok.
  601. Application.Run<Toplevel> ();
  602. Application.Top.Dispose ();
  603. Shutdown ();
  604. Assert.Null (Application.Top);
  605. Assert.Null (Application.MainLoop);
  606. Assert.Null (Application.Driver);
  607. }
  608. [Fact]
  609. [TestRespondersDisposed]
  610. public void Run_T_After_InitNullDriver_with_TestTopLevel_DoesNotThrow ()
  611. {
  612. Application.ForceDriver = "FakeDriver";
  613. Application.Init ();
  614. Assert.Equal (typeof (FakeDriver), Application.Driver?.GetType ());
  615. Application.Iteration += (s, a) => { Application.RequestStop (); };
  616. // Init has been called, selecting FakeDriver; we're passing no driver to Run<TestTopLevel>. Should be fine.
  617. Application.Run<Toplevel> ();
  618. Application.Top.Dispose ();
  619. Shutdown ();
  620. Assert.Null (Application.Top);
  621. Assert.Null (Application.MainLoop);
  622. Assert.Null (Application.Driver);
  623. }
  624. [Fact]
  625. [TestRespondersDisposed]
  626. public void Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws ()
  627. {
  628. Init ();
  629. Application.Driver = null;
  630. // Init has been called, but Driver has been set to null. Bad.
  631. Assert.Throws<InvalidOperationException> (() => Application.Run<Toplevel> ());
  632. Shutdown ();
  633. Assert.Null (Application.Top);
  634. Assert.Null (Application.MainLoop);
  635. Assert.Null (Application.Driver);
  636. }
  637. [Fact]
  638. [TestRespondersDisposed]
  639. public void Run_T_NoInit_DoesNotThrow ()
  640. {
  641. Application.ForceDriver = "FakeDriver";
  642. Application.Iteration += (s, a) => { Application.RequestStop (); };
  643. Application.Run<Toplevel> ();
  644. Assert.Equal (typeof (FakeDriver), Application.Driver?.GetType ());
  645. Application.Top.Dispose ();
  646. Shutdown ();
  647. Assert.Null (Application.Top);
  648. Assert.Null (Application.MainLoop);
  649. Assert.Null (Application.Driver);
  650. }
  651. [Fact]
  652. [TestRespondersDisposed]
  653. public void Run_T_NoInit_WithDriver_DoesNotThrow ()
  654. {
  655. Application.Iteration += (s, a) => { Application.RequestStop (); };
  656. // Init has NOT been called and we're passing a valid driver to Run<TestTopLevel>. This is ok.
  657. Application.Run<Toplevel> (null, new FakeDriver ());
  658. Application.Top.Dispose ();
  659. Shutdown ();
  660. Assert.Null (Application.Top);
  661. Assert.Null (Application.MainLoop);
  662. Assert.Null (Application.Driver);
  663. }
  664. [Fact]
  665. [TestRespondersDisposed]
  666. public void Run_RequestStop_Stops ()
  667. {
  668. // Setup Mock driver
  669. Init ();
  670. var top = new Toplevel ();
  671. RunState rs = Application.Begin (top);
  672. Assert.NotNull (rs);
  673. Application.Iteration += (s, a) => { Application.RequestStop (); };
  674. Application.Run (top);
  675. top.Dispose ();
  676. Application.Shutdown ();
  677. Assert.Null (Application.Top);
  678. Assert.Null (Application.MainLoop);
  679. Assert.Null (Application.Driver);
  680. }
  681. [Fact]
  682. [TestRespondersDisposed]
  683. public void Run_RunningFalse_Stops ()
  684. {
  685. // Setup Mock driver
  686. Init ();
  687. var top = new Toplevel ();
  688. RunState rs = Application.Begin (top);
  689. Assert.NotNull (rs);
  690. Application.Iteration += (s, a) => { top.Running = false; };
  691. Application.Run (top);
  692. top.Dispose ();
  693. Application.Shutdown ();
  694. Assert.Null (Application.Top);
  695. Assert.Null (Application.MainLoop);
  696. Assert.Null (Application.Driver);
  697. }
  698. [Fact]
  699. [TestRespondersDisposed]
  700. public void Run_Loaded_Ready_Unloaded_Events ()
  701. {
  702. Init ();
  703. Toplevel top = new ();
  704. var count = 0;
  705. top.Loaded += (s, e) => count++;
  706. top.Ready += (s, e) => count++;
  707. top.Unloaded += (s, e) => count++;
  708. Application.Iteration += (s, a) => Application.RequestStop ();
  709. Application.Run (top);
  710. top.Dispose ();
  711. Application.Shutdown ();
  712. Assert.Equal (3, count);
  713. }
  714. // TODO: All Toplevel layout tests should be moved to ToplevelTests.cs
  715. [Fact]
  716. public void Run_A_Modal_Toplevel_Refresh_Background_On_Moving ()
  717. {
  718. Init ();
  719. // Don't use Dialog here as it has more layout logic. Use Window instead.
  720. var w = new Window
  721. {
  722. Width = 5, Height = 5,
  723. Arrangement = ViewArrangement.Movable
  724. };
  725. ((FakeDriver)Application.Driver!).SetBufferSize (10, 10);
  726. RunState rs = Application.Begin (w);
  727. // Don't use visuals to test as style of border can change over time.
  728. Assert.Equal (new (0, 0), w.Frame.Location);
  729. Application.RaiseMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
  730. Assert.Equal (w.Border, Application.MouseGrabView);
  731. Assert.Equal (new (0, 0), w.Frame.Location);
  732. // Move down and to the right.
  733. Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition });
  734. Assert.Equal (new (1, 1), w.Frame.Location);
  735. Application.End (rs);
  736. w.Dispose ();
  737. Application.Shutdown ();
  738. }
  739. [Fact]
  740. public void End_Does_Not_Dispose ()
  741. {
  742. Init ();
  743. var top = new Toplevel ();
  744. Window w = new ();
  745. w.Ready += (s, e) => Application.RequestStop (); // Causes `End` to be called
  746. Application.Run (w);
  747. #if DEBUG_IDISPOSABLE
  748. Assert.False (w.WasDisposed);
  749. #endif
  750. Assert.NotNull (w);
  751. Assert.Equal (string.Empty, w.Title); // Valid - w has not been disposed. The user may want to run it again
  752. Assert.NotNull (Application.Top);
  753. Assert.Equal (w, Application.Top);
  754. Assert.NotEqual (top, Application.Top);
  755. Application.Run (w); // Valid - w has not been disposed.
  756. #if DEBUG_IDISPOSABLE
  757. Assert.False (w.WasDisposed);
  758. Exception exception = Record.Exception (Application.Shutdown); // Invalid - w has not been disposed.
  759. Assert.NotNull (exception);
  760. w.Dispose ();
  761. Assert.True (w.WasDisposed);
  762. //exception = Record.Exception (
  763. // () => Application.Run (
  764. // w)); // Invalid - w has been disposed. Run it in debug mode will throw, otherwise the user may want to run it again
  765. //Assert.NotNull (exception);
  766. exception = Record.Exception (() => Assert.Equal (string.Empty, w.Title)); // Invalid - w has been disposed and cannot be accessed
  767. Assert.NotNull (exception);
  768. exception = Record.Exception (() => w.Title = "NewTitle"); // Invalid - w has been disposed and cannot be accessed
  769. Assert.NotNull (exception);
  770. #endif
  771. Application.Shutdown ();
  772. Assert.NotNull (w);
  773. Assert.NotNull (top);
  774. Assert.Null (Application.Top);
  775. }
  776. [Fact]
  777. public void Run_Creates_Top_Without_Init ()
  778. {
  779. var driver = new FakeDriver ();
  780. Assert.Null (Application.Top);
  781. Application.Iteration += (s, e) =>
  782. {
  783. Assert.NotNull (Application.Top);
  784. Application.RequestStop ();
  785. };
  786. Toplevel top = Application.Run (null, driver);
  787. #if DEBUG_IDISPOSABLE
  788. Assert.Equal (top, Application.Top);
  789. Assert.False (top.WasDisposed);
  790. Exception exception = Record.Exception (Application.Shutdown);
  791. Assert.NotNull (exception);
  792. Assert.False (top.WasDisposed);
  793. #endif
  794. // It's up to caller to dispose it
  795. top.Dispose ();
  796. #if DEBUG_IDISPOSABLE
  797. Assert.True (top.WasDisposed);
  798. #endif
  799. Assert.NotNull (Application.Top);
  800. Application.Shutdown ();
  801. Assert.Null (Application.Top);
  802. }
  803. [Fact]
  804. public void Run_T_Creates_Top_Without_Init ()
  805. {
  806. var driver = new FakeDriver ();
  807. Assert.Null (Application.Top);
  808. Application.Iteration += (s, e) =>
  809. {
  810. Assert.NotNull (Application.Top);
  811. Application.RequestStop ();
  812. };
  813. Application.Run<Toplevel> (null, driver);
  814. #if DEBUG_IDISPOSABLE
  815. Assert.False (Application.Top.WasDisposed);
  816. Exception exception = Record.Exception (Application.Shutdown);
  817. Assert.NotNull (exception);
  818. Assert.False (Application.Top.WasDisposed);
  819. // It's up to caller to dispose it
  820. Application.Top.Dispose ();
  821. Assert.True (Application.Top.WasDisposed);
  822. #endif
  823. Assert.NotNull (Application.Top);
  824. Application.Shutdown ();
  825. Assert.Null (Application.Top);
  826. }
  827. [Fact]
  828. public void Run_t_Does_Not_Creates_Top_Without_Init ()
  829. {
  830. // When a Toplevel is created it must already have all the Application configuration loaded
  831. // This is only possible by two ways:
  832. // 1 - Using Application.Init first
  833. // 2 - Using Application.Run() or Application.Run<T>()
  834. // The Application.Run(new(Toplevel)) must always call Application.Init() first because
  835. // the new(Toplevel) may be a derived class that is possible using Application static
  836. // properties that is only available after the Application.Init was called
  837. var driver = new FakeDriver ();
  838. Assert.Null (Application.Top);
  839. Assert.Throws<InvalidOperationException> (() => Application.Run (new Toplevel ()));
  840. Application.Init (driver);
  841. Application.Iteration += (s, e) =>
  842. {
  843. Assert.NotNull (Application.Top);
  844. Application.RequestStop ();
  845. };
  846. Application.Run (new Toplevel ());
  847. #if DEBUG_IDISPOSABLE
  848. Assert.False (Application.Top.WasDisposed);
  849. Exception exception = Record.Exception (Application.Shutdown);
  850. Assert.NotNull (exception);
  851. Assert.False (Application.Top.WasDisposed);
  852. // It's up to caller to dispose it
  853. Application.Top.Dispose ();
  854. Assert.True (Application.Top.WasDisposed);
  855. #endif
  856. Assert.NotNull (Application.Top);
  857. Application.Shutdown ();
  858. Assert.Null (Application.Top);
  859. }
  860. // TODO: Add tests for Run that test errorHandler
  861. #endregion
  862. #region ShutdownTests
  863. [Fact]
  864. public async Task Shutdown_Allows_Async ()
  865. {
  866. var isCompletedSuccessfully = false;
  867. async Task TaskWithAsyncContinuation ()
  868. {
  869. await Task.Yield ();
  870. await Task.Yield ();
  871. isCompletedSuccessfully = true;
  872. }
  873. Init ();
  874. Application.Shutdown ();
  875. Assert.False (isCompletedSuccessfully);
  876. await TaskWithAsyncContinuation ();
  877. Thread.Sleep (100);
  878. Assert.True (isCompletedSuccessfully);
  879. }
  880. [Fact]
  881. public void Shutdown_Resets_SyncContext ()
  882. {
  883. Init ();
  884. Application.Shutdown ();
  885. Assert.Null (SynchronizationContext.Current);
  886. }
  887. #endregion
  888. }