ApplicationTests.cs 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129
  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. // Do not reset _lastMousePosition
  266. //Assert.Null (Application._lastMousePosition);
  267. // Navigation
  268. Assert.Null (Application.Navigation);
  269. // Events - Can't check
  270. //Assert.Null (Application.NotifyNewRunState);
  271. //Assert.Null (Application.NotifyNewRunState);
  272. //Assert.Null (Application.Iteration);
  273. //Assert.Null (Application.SizeChanging);
  274. //Assert.Null (Application.GrabbedMouse);
  275. //Assert.Null (Application.UnGrabbingMouse);
  276. //Assert.Null (Application.GrabbedMouse);
  277. //Assert.Null (Application.UnGrabbedMouse);
  278. //Assert.Null (Application.MouseEvent);
  279. //Assert.Null (Application.KeyDown);
  280. //Assert.Null (Application.KeyUp);
  281. }
  282. CheckReset ();
  283. // Set the values that can be set
  284. Application.Initialized = true;
  285. Application._forceFakeConsole = true;
  286. Application.MainThreadId = 1;
  287. //Application._topLevels = new List<Toplevel> ();
  288. Application._cachedViewsUnderMouse.Clear ();
  289. //Application.SupportedCultures = new List<CultureInfo> ();
  290. Application.Force16Colors = true;
  291. //Application.ForceDriver = "driver";
  292. Application.EndAfterFirstIteration = true;
  293. Application.PrevTabGroupKey = Key.A;
  294. Application.NextTabGroupKey = Key.B;
  295. Application.QuitKey = Key.C;
  296. Application.KeyBindings.Add (Key.D, Command.Cancel);
  297. Application._cachedViewsUnderMouse.Clear ();
  298. //Application.WantContinuousButtonPressedView = new View ();
  299. // Mouse
  300. Application._lastMousePosition = new Point (1, 1);
  301. Application.Navigation = new ();
  302. Application.ResetState ();
  303. CheckReset ();
  304. ThrowOnJsonErrors = false;
  305. }
  306. [Fact]
  307. public void Init_Shutdown_Cleans_Up ()
  308. {
  309. // Verify initial state is per spec
  310. //Pre_Init_State ();
  311. Application.Init (new FakeDriver ());
  312. // Verify post-Init state is correct
  313. //Post_Init_State ();
  314. Application.Shutdown ();
  315. // Verify state is back to initial
  316. //Pre_Init_State ();
  317. #if DEBUG_IDISPOSABLE
  318. // Validate there are no outstanding Responder-based instances
  319. // after a scenario was selected to run. This proves the main UI Catalog
  320. // 'app' closed cleanly.
  321. Assert.Empty (View.Instances);
  322. #endif
  323. }
  324. [Fact]
  325. public void Shutdown_Alone_Does_Nothing () { Application.Shutdown (); }
  326. [Theory]
  327. [InlineData (typeof (FakeDriver))]
  328. [InlineData (typeof (NetDriver))]
  329. [InlineData (typeof (WindowsDriver))]
  330. [InlineData (typeof (CursesDriver))]
  331. public void Init_Shutdown_Fire_InitializedChanged (Type driverType)
  332. {
  333. var initialized = false;
  334. var shutdown = false;
  335. Application.InitializedChanged += OnApplicationOnInitializedChanged;
  336. Application.Init (driverName: driverType.Name);
  337. Assert.True (initialized);
  338. Assert.False (shutdown);
  339. Application.Shutdown ();
  340. Assert.True (initialized);
  341. Assert.True (shutdown);
  342. Application.InitializedChanged -= OnApplicationOnInitializedChanged;
  343. return;
  344. void OnApplicationOnInitializedChanged (object s, EventArgs<bool> a)
  345. {
  346. if (a.CurrentValue)
  347. {
  348. initialized = true;
  349. }
  350. else
  351. {
  352. shutdown = true;
  353. }
  354. }
  355. }
  356. [Fact]
  357. public void Init_Unbalanced_Throws ()
  358. {
  359. Application.Init (new FakeDriver ());
  360. Assert.Throws<InvalidOperationException> (
  361. () =>
  362. Application.InternalInit (
  363. new FakeDriver ()
  364. )
  365. );
  366. Shutdown ();
  367. Assert.Null (Application.Top);
  368. Assert.Null (Application.MainLoop);
  369. Assert.Null (Application.Driver);
  370. // Now try the other way
  371. Application.InternalInit (new FakeDriver ());
  372. Assert.Throws<InvalidOperationException> (() => Application.Init (new FakeDriver ()));
  373. Shutdown ();
  374. Assert.Null (Application.Top);
  375. Assert.Null (Application.MainLoop);
  376. Assert.Null (Application.Driver);
  377. }
  378. [Fact]
  379. public void Init_WithoutTopLevelFactory_Begin_End_Cleans_Up ()
  380. {
  381. // Begin will cause Run() to be called, which will call Begin(). Thus will block the tests
  382. // if we don't stop
  383. Application.Iteration += (s, a) => { Application.RequestStop (); };
  384. // NOTE: Run<T>, when called after Init has been called behaves differently than
  385. // when called if Init has not been called.
  386. Toplevel topLevel = new ();
  387. Application.InternalInit (new FakeDriver ());
  388. RunState runstate = null;
  389. EventHandler<RunStateEventArgs> newRunStateFn = (s, e) =>
  390. {
  391. Assert.NotNull (e.State);
  392. runstate = e.State;
  393. };
  394. Application.NotifyNewRunState += newRunStateFn;
  395. RunState rs = Application.Begin (topLevel);
  396. Assert.NotNull (rs);
  397. Assert.NotNull (runstate);
  398. Assert.Equal (rs, runstate);
  399. Assert.Equal (topLevel, Application.Top);
  400. Application.NotifyNewRunState -= newRunStateFn;
  401. Application.End (runstate);
  402. Assert.NotNull (Application.Top);
  403. Assert.NotNull (Application.MainLoop);
  404. Assert.NotNull (Application.Driver);
  405. topLevel.Dispose ();
  406. Shutdown ();
  407. Assert.Null (Application.Top);
  408. Assert.Null (Application.MainLoop);
  409. Assert.Null (Application.Driver);
  410. }
  411. [Fact]
  412. public void Init_NoParam_ForceDriver_Works ()
  413. {
  414. Application.ForceDriver = "FakeDriver";
  415. Application.Init ();
  416. Assert.IsType<FakeDriver> (Application.Driver);
  417. Application.ResetState ();
  418. }
  419. [Fact]
  420. public void Init_KeyBindings_Set_To_Defaults ()
  421. {
  422. // arrange
  423. Locations = ConfigLocations.All;
  424. ThrowOnJsonErrors = true;
  425. Application.QuitKey = Key.Q;
  426. Application.Init (new FakeDriver ());
  427. Assert.Equal (Key.Esc, Application.QuitKey);
  428. Application.Shutdown ();
  429. }
  430. [Fact]
  431. public void Init_KeyBindings_Set_To_Custom ()
  432. {
  433. // arrange
  434. Locations = ConfigLocations.Runtime;
  435. ThrowOnJsonErrors = true;
  436. RuntimeConfig = """
  437. {
  438. "Application.QuitKey": "Ctrl-Q"
  439. }
  440. """;
  441. Assert.Equal (Key.Esc, Application.QuitKey);
  442. // Act
  443. Application.Init (new FakeDriver ());
  444. Assert.Equal (Key.Q.WithCtrl, Application.QuitKey);
  445. Assert.True (Application.KeyBindings.TryGet (Key.Q.WithCtrl, out _));
  446. Application.Shutdown ();
  447. Locations = ConfigLocations.Default;
  448. }
  449. [Fact]
  450. [AutoInitShutdown (verifyShutdown: true)]
  451. public void Internal_Properties_Correct ()
  452. {
  453. Assert.True (Application.Initialized);
  454. Assert.Null (Application.Top);
  455. RunState rs = Application.Begin (new ());
  456. Assert.Equal (Application.Top, rs.Toplevel);
  457. Assert.Null (Application.MouseGrabView); // public
  458. Assert.Null (Application.WantContinuousButtonPressedView); // public
  459. Application.Top.Dispose ();
  460. }
  461. // Invoke Tests
  462. // TODO: Test with threading scenarios
  463. [Fact]
  464. public void Invoke_Adds_Idle ()
  465. {
  466. Application.Init (new FakeDriver ());
  467. var top = new Toplevel ();
  468. RunState rs = Application.Begin (top);
  469. var firstIteration = false;
  470. var actionCalled = 0;
  471. Application.Invoke (() => { actionCalled++; });
  472. Application.MainLoop.Running = true;
  473. Application.RunIteration (ref rs, firstIteration);
  474. Assert.Equal (1, actionCalled);
  475. top.Dispose ();
  476. Application.Shutdown ();
  477. }
  478. [Fact]
  479. public void Run_Iteration_Fires ()
  480. {
  481. var iteration = 0;
  482. Application.Init (new FakeDriver ());
  483. Application.Iteration += Application_Iteration;
  484. Application.Run<Toplevel> ().Dispose ();
  485. Assert.Equal (1, iteration);
  486. Application.Shutdown ();
  487. return;
  488. void Application_Iteration (object sender, IterationEventArgs e)
  489. {
  490. if (iteration > 0)
  491. {
  492. Assert.Fail ();
  493. }
  494. iteration++;
  495. Application.RequestStop ();
  496. }
  497. }
  498. [Fact]
  499. public void Screen_Size_Changes ()
  500. {
  501. var driver = new FakeDriver ();
  502. Application.Init (driver);
  503. Assert.Equal (new (0, 0, 80, 25), driver.Screen);
  504. Assert.Equal (new (0, 0, 80, 25), Application.Screen);
  505. driver.Cols = 100;
  506. driver.Rows = 30;
  507. // IConsoleDriver.Screen isn't assignable
  508. //driver.Screen = new (0, 0, driver.Cols, Rows);
  509. Assert.Equal (new (0, 0, 100, 30), driver.Screen);
  510. Assert.NotEqual (new (0, 0, 100, 30), Application.Screen);
  511. Assert.Equal (new (0, 0, 80, 25), Application.Screen);
  512. Application.Screen = new (0, 0, driver.Cols, driver.Rows);
  513. Assert.Equal (new (0, 0, 100, 30), driver.Screen);
  514. Application.Shutdown ();
  515. }
  516. [Fact]
  517. public void InitState_Throws_If_Driver_Is_Null ()
  518. {
  519. Assert.Throws<ArgumentNullException> (static () => Application.SubscribeDriverEvents ());
  520. }
  521. private void Init ()
  522. {
  523. Application.Init (new FakeDriver ());
  524. Assert.NotNull (Application.Driver);
  525. Assert.NotNull (Application.MainLoop);
  526. Assert.NotNull (SynchronizationContext.Current);
  527. }
  528. private void Shutdown () { Application.Shutdown (); }
  529. #region RunTests
  530. [Fact]
  531. public void Run_T_After_InitWithDriver_with_TopLevel_Does_Not_Throws ()
  532. {
  533. // Setup Mock driver
  534. Init ();
  535. Application.Iteration += (s, e) => Application.RequestStop ();
  536. // Run<Toplevel> when already initialized or not with a Driver will not throw (because Window is derived from Toplevel)
  537. // Using another type not derived from Toplevel will throws at compile time
  538. Application.Run<Window> ();
  539. Assert.True (Application.Top is Window);
  540. Application.Top.Dispose ();
  541. Shutdown ();
  542. Assert.Null (Application.Top);
  543. Assert.Null (Application.MainLoop);
  544. Assert.Null (Application.Driver);
  545. }
  546. [Fact]
  547. public void Run_T_After_InitWithDriver_with_TopLevel_and_Driver_Does_Not_Throws ()
  548. {
  549. // Setup Mock driver
  550. Init ();
  551. Application.Iteration += (s, e) => Application.RequestStop ();
  552. // Run<Toplevel> when already initialized or not with a Driver will not throw (because Window is derived from Toplevel)
  553. // Using another type not derived from Toplevel will throws at compile time
  554. Application.Run<Window> (null, new FakeDriver ());
  555. Assert.True (Application.Top is Window);
  556. Application.Top.Dispose ();
  557. // Run<Toplevel> when already initialized or not with a Driver will not throw (because Dialog is derived from Toplevel)
  558. Application.Run<Dialog> (null, new FakeDriver ());
  559. Assert.True (Application.Top is Dialog);
  560. Application.Top.Dispose ();
  561. Shutdown ();
  562. Assert.Null (Application.Top);
  563. Assert.Null (Application.MainLoop);
  564. Assert.Null (Application.Driver);
  565. }
  566. [Fact]
  567. [TestRespondersDisposed]
  568. public void Run_T_After_Init_Does_Not_Disposes_Application_Top ()
  569. {
  570. Init ();
  571. // Init doesn't create a Toplevel and assigned it to Application.Top
  572. // but Begin does
  573. var initTop = new Toplevel ();
  574. Application.Iteration += (s, a) =>
  575. {
  576. Assert.NotEqual (initTop, Application.Top);
  577. #if DEBUG_IDISPOSABLE
  578. Assert.False (initTop.WasDisposed);
  579. #endif
  580. Application.RequestStop ();
  581. };
  582. Application.Run<Toplevel> ();
  583. #if DEBUG_IDISPOSABLE
  584. Assert.False (initTop.WasDisposed);
  585. initTop.Dispose ();
  586. Assert.True (initTop.WasDisposed);
  587. #endif
  588. Application.Top.Dispose ();
  589. Shutdown ();
  590. Assert.Null (Application.Top);
  591. Assert.Null (Application.MainLoop);
  592. Assert.Null (Application.Driver);
  593. }
  594. [Fact]
  595. [TestRespondersDisposed]
  596. public void Run_T_After_InitWithDriver_with_TestTopLevel_DoesNotThrow ()
  597. {
  598. // Setup Mock driver
  599. Init ();
  600. Application.Iteration += (s, a) => { Application.RequestStop (); };
  601. // Init has been called and we're passing no driver to Run<TestTopLevel>. This is ok.
  602. Application.Run<Toplevel> ();
  603. Application.Top.Dispose ();
  604. Shutdown ();
  605. Assert.Null (Application.Top);
  606. Assert.Null (Application.MainLoop);
  607. Assert.Null (Application.Driver);
  608. }
  609. [Fact]
  610. [TestRespondersDisposed]
  611. public void Run_T_After_InitNullDriver_with_TestTopLevel_DoesNotThrow ()
  612. {
  613. Application.ForceDriver = "FakeDriver";
  614. Application.Init ();
  615. Assert.Equal (typeof (FakeDriver), Application.Driver?.GetType ());
  616. Application.Iteration += (s, a) => { Application.RequestStop (); };
  617. // Init has been called, selecting FakeDriver; we're passing no driver to Run<TestTopLevel>. Should be fine.
  618. Application.Run<Toplevel> ();
  619. Application.Top.Dispose ();
  620. Shutdown ();
  621. Assert.Null (Application.Top);
  622. Assert.Null (Application.MainLoop);
  623. Assert.Null (Application.Driver);
  624. }
  625. [Fact]
  626. [TestRespondersDisposed]
  627. public void Run_T_Init_Driver_Cleared_with_TestTopLevel_Throws ()
  628. {
  629. Init ();
  630. Application.Driver = null;
  631. // Init has been called, but Driver has been set to null. Bad.
  632. Assert.Throws<InvalidOperationException> (() => Application.Run<Toplevel> ());
  633. Shutdown ();
  634. Assert.Null (Application.Top);
  635. Assert.Null (Application.MainLoop);
  636. Assert.Null (Application.Driver);
  637. }
  638. [Fact]
  639. [TestRespondersDisposed]
  640. public void Run_T_NoInit_DoesNotThrow ()
  641. {
  642. Application.ForceDriver = "FakeDriver";
  643. Application.Iteration += (s, a) => { Application.RequestStop (); };
  644. Application.Run<Toplevel> ();
  645. Assert.Equal (typeof (FakeDriver), Application.Driver?.GetType ());
  646. Application.Top.Dispose ();
  647. Shutdown ();
  648. Assert.Null (Application.Top);
  649. Assert.Null (Application.MainLoop);
  650. Assert.Null (Application.Driver);
  651. }
  652. [Fact]
  653. [TestRespondersDisposed]
  654. public void Run_T_NoInit_WithDriver_DoesNotThrow ()
  655. {
  656. Application.Iteration += (s, a) => { Application.RequestStop (); };
  657. // Init has NOT been called and we're passing a valid driver to Run<TestTopLevel>. This is ok.
  658. Application.Run<Toplevel> (null, new FakeDriver ());
  659. Application.Top.Dispose ();
  660. Shutdown ();
  661. Assert.Null (Application.Top);
  662. Assert.Null (Application.MainLoop);
  663. Assert.Null (Application.Driver);
  664. }
  665. [Fact]
  666. [TestRespondersDisposed]
  667. public void Run_RequestStop_Stops ()
  668. {
  669. // Setup Mock driver
  670. Init ();
  671. var top = new Toplevel ();
  672. RunState rs = Application.Begin (top);
  673. Assert.NotNull (rs);
  674. Application.Iteration += (s, a) => { Application.RequestStop (); };
  675. Application.Run (top);
  676. top.Dispose ();
  677. Application.Shutdown ();
  678. Assert.Null (Application.Top);
  679. Assert.Null (Application.MainLoop);
  680. Assert.Null (Application.Driver);
  681. }
  682. [Fact]
  683. [TestRespondersDisposed]
  684. public void Run_RunningFalse_Stops ()
  685. {
  686. // Setup Mock driver
  687. Init ();
  688. var top = new Toplevel ();
  689. RunState rs = Application.Begin (top);
  690. Assert.NotNull (rs);
  691. Application.Iteration += (s, a) => { top.Running = false; };
  692. Application.Run (top);
  693. top.Dispose ();
  694. Application.Shutdown ();
  695. Assert.Null (Application.Top);
  696. Assert.Null (Application.MainLoop);
  697. Assert.Null (Application.Driver);
  698. }
  699. [Fact]
  700. [TestRespondersDisposed]
  701. public void Run_Loaded_Ready_Unloaded_Events ()
  702. {
  703. Init ();
  704. Toplevel top = new ();
  705. var count = 0;
  706. top.Loaded += (s, e) => count++;
  707. top.Ready += (s, e) => count++;
  708. top.Unloaded += (s, e) => count++;
  709. Application.Iteration += (s, a) => Application.RequestStop ();
  710. Application.Run (top);
  711. top.Dispose ();
  712. Application.Shutdown ();
  713. Assert.Equal (3, count);
  714. }
  715. // TODO: All Toplevel layout tests should be moved to ToplevelTests.cs
  716. [Fact]
  717. public void Run_A_Modal_Toplevel_Refresh_Background_On_Moving ()
  718. {
  719. Init ();
  720. // Don't use Dialog here as it has more layout logic. Use Window instead.
  721. var w = new Window
  722. {
  723. Width = 5, Height = 5,
  724. Arrangement = ViewArrangement.Movable
  725. };
  726. ((FakeDriver)Application.Driver!).SetBufferSize (10, 10);
  727. RunState rs = Application.Begin (w);
  728. // Don't use visuals to test as style of border can change over time.
  729. Assert.Equal (new (0, 0), w.Frame.Location);
  730. Application.RaiseMouseEvent (new () { Flags = MouseFlags.Button1Pressed });
  731. Assert.Equal (w.Border, Application.MouseGrabView);
  732. Assert.Equal (new (0, 0), w.Frame.Location);
  733. // Move down and to the right.
  734. Application.RaiseMouseEvent (new () { ScreenPosition = new (1, 1), Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition });
  735. Assert.Equal (new (1, 1), w.Frame.Location);
  736. Application.End (rs);
  737. w.Dispose ();
  738. Application.Shutdown ();
  739. }
  740. [Fact]
  741. public void End_Does_Not_Dispose ()
  742. {
  743. Init ();
  744. var top = new Toplevel ();
  745. Window w = new ();
  746. w.Ready += (s, e) => Application.RequestStop (); // Causes `End` to be called
  747. Application.Run (w);
  748. #if DEBUG_IDISPOSABLE
  749. Assert.False (w.WasDisposed);
  750. #endif
  751. Assert.NotNull (w);
  752. Assert.Equal (string.Empty, w.Title); // Valid - w has not been disposed. The user may want to run it again
  753. Assert.NotNull (Application.Top);
  754. Assert.Equal (w, Application.Top);
  755. Assert.NotEqual (top, Application.Top);
  756. Application.Run (w); // Valid - w has not been disposed.
  757. #if DEBUG_IDISPOSABLE
  758. Assert.False (w.WasDisposed);
  759. Exception exception = Record.Exception (Application.Shutdown); // Invalid - w has not been disposed.
  760. Assert.NotNull (exception);
  761. w.Dispose ();
  762. Assert.True (w.WasDisposed);
  763. //exception = Record.Exception (
  764. // () => Application.Run (
  765. // w)); // Invalid - w has been disposed. Run it in debug mode will throw, otherwise the user may want to run it again
  766. //Assert.NotNull (exception);
  767. exception = Record.Exception (() => Assert.Equal (string.Empty, w.Title)); // Invalid - w has been disposed and cannot be accessed
  768. Assert.NotNull (exception);
  769. exception = Record.Exception (() => w.Title = "NewTitle"); // Invalid - w has been disposed and cannot be accessed
  770. Assert.NotNull (exception);
  771. #endif
  772. Application.Shutdown ();
  773. Assert.NotNull (w);
  774. Assert.NotNull (top);
  775. Assert.Null (Application.Top);
  776. }
  777. [Fact]
  778. public void Run_Creates_Top_Without_Init ()
  779. {
  780. var driver = new FakeDriver ();
  781. Assert.Null (Application.Top);
  782. Application.Iteration += (s, e) =>
  783. {
  784. Assert.NotNull (Application.Top);
  785. Application.RequestStop ();
  786. };
  787. Toplevel top = Application.Run (null, driver);
  788. #if DEBUG_IDISPOSABLE
  789. Assert.Equal (top, Application.Top);
  790. Assert.False (top.WasDisposed);
  791. Exception exception = Record.Exception (Application.Shutdown);
  792. Assert.NotNull (exception);
  793. Assert.False (top.WasDisposed);
  794. #endif
  795. // It's up to caller to dispose it
  796. top.Dispose ();
  797. #if DEBUG_IDISPOSABLE
  798. Assert.True (top.WasDisposed);
  799. #endif
  800. Assert.NotNull (Application.Top);
  801. Application.Shutdown ();
  802. Assert.Null (Application.Top);
  803. }
  804. [Fact]
  805. public void Run_T_Creates_Top_Without_Init ()
  806. {
  807. var driver = new FakeDriver ();
  808. Assert.Null (Application.Top);
  809. Application.Iteration += (s, e) =>
  810. {
  811. Assert.NotNull (Application.Top);
  812. Application.RequestStop ();
  813. };
  814. Application.Run<Toplevel> (null, driver);
  815. #if DEBUG_IDISPOSABLE
  816. Assert.False (Application.Top.WasDisposed);
  817. Exception exception = Record.Exception (Application.Shutdown);
  818. Assert.NotNull (exception);
  819. Assert.False (Application.Top.WasDisposed);
  820. // It's up to caller to dispose it
  821. Application.Top.Dispose ();
  822. Assert.True (Application.Top.WasDisposed);
  823. #endif
  824. Assert.NotNull (Application.Top);
  825. Application.Shutdown ();
  826. Assert.Null (Application.Top);
  827. }
  828. [Fact]
  829. public void Run_t_Does_Not_Creates_Top_Without_Init ()
  830. {
  831. // When a Toplevel is created it must already have all the Application configuration loaded
  832. // This is only possible by two ways:
  833. // 1 - Using Application.Init first
  834. // 2 - Using Application.Run() or Application.Run<T>()
  835. // The Application.Run(new(Toplevel)) must always call Application.Init() first because
  836. // the new(Toplevel) may be a derived class that is possible using Application static
  837. // properties that is only available after the Application.Init was called
  838. var driver = new FakeDriver ();
  839. Assert.Null (Application.Top);
  840. Assert.Throws<InvalidOperationException> (() => Application.Run (new Toplevel ()));
  841. Application.Init (driver);
  842. Application.Iteration += (s, e) =>
  843. {
  844. Assert.NotNull (Application.Top);
  845. Application.RequestStop ();
  846. };
  847. Application.Run (new Toplevel ());
  848. #if DEBUG_IDISPOSABLE
  849. Assert.False (Application.Top.WasDisposed);
  850. Exception exception = Record.Exception (Application.Shutdown);
  851. Assert.NotNull (exception);
  852. Assert.False (Application.Top.WasDisposed);
  853. // It's up to caller to dispose it
  854. Application.Top.Dispose ();
  855. Assert.True (Application.Top.WasDisposed);
  856. #endif
  857. Assert.NotNull (Application.Top);
  858. Application.Shutdown ();
  859. Assert.Null (Application.Top);
  860. }
  861. // TODO: Add tests for Run that test errorHandler
  862. #endregion
  863. #region ShutdownTests
  864. [Fact]
  865. public async Task Shutdown_Allows_Async ()
  866. {
  867. var isCompletedSuccessfully = false;
  868. async Task TaskWithAsyncContinuation ()
  869. {
  870. await Task.Yield ();
  871. await Task.Yield ();
  872. isCompletedSuccessfully = true;
  873. }
  874. Init ();
  875. Application.Shutdown ();
  876. Assert.False (isCompletedSuccessfully);
  877. await TaskWithAsyncContinuation ();
  878. Thread.Sleep (100);
  879. Assert.True (isCompletedSuccessfully);
  880. }
  881. [Fact]
  882. public void Shutdown_Resets_SyncContext ()
  883. {
  884. Init ();
  885. Application.Shutdown ();
  886. Assert.Null (SynchronizationContext.Current);
  887. }
  888. #endregion
  889. }