IApplicationScreenChangedTests.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. using Xunit.Abstractions;
  2. namespace ApplicationTests;
  3. /// <summary>
  4. /// Parallelizable tests for IApplication.ScreenChanged event and Screen property.
  5. /// Tests using the modern instance-based IApplication API.
  6. /// </summary>
  7. public class IApplicationScreenChangedTests (ITestOutputHelper output)
  8. {
  9. private readonly ITestOutputHelper _output = output;
  10. #region ScreenChanged Event Tests
  11. [Fact]
  12. public void ScreenChanged_Event_Fires_When_Driver_Size_Changes ()
  13. {
  14. // Arrange
  15. using IApplication app = Application.Create ();
  16. app.Init ("fake");
  17. var eventFired = false;
  18. Rectangle? newScreen = null;
  19. EventHandler<EventArgs<Rectangle>> handler = (sender, args) =>
  20. {
  21. eventFired = true;
  22. newScreen = args.Value;
  23. };
  24. app.ScreenChanged += handler;
  25. try
  26. {
  27. // Act
  28. app.Driver!.SetScreenSize (100, 40);
  29. // Assert
  30. Assert.True (eventFired);
  31. Assert.NotNull (newScreen);
  32. Assert.Equal (new (0, 0, 100, 40), newScreen.Value);
  33. }
  34. finally
  35. {
  36. app.ScreenChanged -= handler;
  37. }
  38. }
  39. [Fact]
  40. public void ScreenChanged_Event_Updates_Application_Screen_Property ()
  41. {
  42. // Arrange
  43. using IApplication app = Application.Create ();
  44. app.Init ("fake");
  45. Rectangle initialScreen = app.Screen;
  46. Assert.Equal (new (0, 0, 80, 25), initialScreen);
  47. // Act
  48. app.Driver!.SetScreenSize (120, 50);
  49. // Assert
  50. Assert.Equal (new (0, 0, 120, 50), app.Screen);
  51. }
  52. [Fact]
  53. public void ScreenChanged_Event_Sender_Is_IApplication ()
  54. {
  55. // Arrange
  56. using IApplication app = Application.Create ();
  57. app.Init ("fake");
  58. object? eventSender = null;
  59. EventHandler<EventArgs<Rectangle>> handler = (sender, args) => { eventSender = sender; };
  60. app.ScreenChanged += handler;
  61. try
  62. {
  63. // Act
  64. app.Driver!.SetScreenSize (100, 30);
  65. // Assert
  66. Assert.NotNull (eventSender);
  67. Assert.IsAssignableFrom<IApplication> (eventSender);
  68. }
  69. finally
  70. {
  71. app.ScreenChanged -= handler;
  72. }
  73. }
  74. [Fact]
  75. public void ScreenChanged_Event_Provides_Correct_Rectangle_In_EventArgs ()
  76. {
  77. // Arrange
  78. using IApplication app = Application.Create ();
  79. app.Init ("fake");
  80. Rectangle? capturedRectangle = null;
  81. EventHandler<EventArgs<Rectangle>> handler = (sender, args) => { capturedRectangle = args.Value; };
  82. app.ScreenChanged += handler;
  83. try
  84. {
  85. // Act
  86. app.Driver!.SetScreenSize (200, 60);
  87. // Assert
  88. Assert.NotNull (capturedRectangle);
  89. Assert.Equal (0, capturedRectangle.Value.X);
  90. Assert.Equal (0, capturedRectangle.Value.Y);
  91. Assert.Equal (200, capturedRectangle.Value.Width);
  92. Assert.Equal (60, capturedRectangle.Value.Height);
  93. }
  94. finally
  95. {
  96. app.ScreenChanged -= handler;
  97. }
  98. }
  99. [Fact]
  100. public void ScreenChanged_Event_Fires_Multiple_Times_For_Multiple_Resizes ()
  101. {
  102. // Arrange
  103. using IApplication app = Application.Create ();
  104. app.Init ("fake");
  105. var eventCount = 0;
  106. List<Size> sizes = new ();
  107. EventHandler<EventArgs<Rectangle>> handler = (sender, args) =>
  108. {
  109. eventCount++;
  110. sizes.Add (args.Value.Size);
  111. };
  112. app.ScreenChanged += handler;
  113. try
  114. {
  115. // Act
  116. app.Driver!.SetScreenSize (100, 30);
  117. app.Driver!.SetScreenSize (120, 40);
  118. app.Driver!.SetScreenSize (80, 25);
  119. // Assert
  120. Assert.Equal (3, eventCount);
  121. Assert.Equal (3, sizes.Count);
  122. Assert.Equal (new (100, 30), sizes [0]);
  123. Assert.Equal (new (120, 40), sizes [1]);
  124. Assert.Equal (new (80, 25), sizes [2]);
  125. }
  126. finally
  127. {
  128. app.ScreenChanged -= handler;
  129. }
  130. }
  131. [Fact]
  132. public void ScreenChanged_Event_Does_Not_Fire_When_No_Resize_Occurs ()
  133. {
  134. // Arrange
  135. using IApplication app = Application.Create ();
  136. app.Init ("fake");
  137. var eventFired = false;
  138. EventHandler<EventArgs<Rectangle>> handler = (sender, args) => { eventFired = true; };
  139. app.ScreenChanged += handler;
  140. try
  141. {
  142. // Act - Don't resize, just access Screen property
  143. Rectangle screen = app.Screen;
  144. // Assert
  145. Assert.False (eventFired);
  146. Assert.Equal (new (0, 0, 80, 25), screen);
  147. }
  148. finally
  149. {
  150. app.ScreenChanged -= handler;
  151. }
  152. }
  153. [Fact]
  154. public void ScreenChanged_Event_Can_Be_Unsubscribed ()
  155. {
  156. // Arrange
  157. using IApplication app = Application.Create ();
  158. app.Init ("fake");
  159. var eventCount = 0;
  160. EventHandler<EventArgs<Rectangle>> handler = (sender, args) => { eventCount++; };
  161. app.ScreenChanged += handler;
  162. // Act - First resize should fire
  163. app.Driver!.SetScreenSize (100, 30);
  164. Assert.Equal (1, eventCount);
  165. // Unsubscribe
  166. app.ScreenChanged -= handler;
  167. // Second resize should not fire
  168. app.Driver!.SetScreenSize (120, 40);
  169. // Assert
  170. Assert.Equal (1, eventCount);
  171. }
  172. [Fact]
  173. public void ScreenChanged_Event_Sets_Runnables_To_NeedsLayout ()
  174. {
  175. // Arrange
  176. using IApplication app = Application.Create ();
  177. app.Init ("fake");
  178. using var runnable = new Runnable ();
  179. SessionToken? token = app.Begin (runnable);
  180. Assert.NotNull (app.TopRunnableView);
  181. app.LayoutAndDraw ();
  182. // Clear the NeedsLayout flag
  183. Assert.False (app.TopRunnableView.NeedsLayout);
  184. try
  185. {
  186. // Act
  187. app.Driver!.SetScreenSize (100, 30);
  188. // Assert
  189. Assert.True (app.TopRunnableView.NeedsLayout);
  190. }
  191. finally
  192. {
  193. // Cleanup
  194. if (token is { })
  195. {
  196. app.End (token);
  197. }
  198. }
  199. }
  200. [Fact]
  201. public void ScreenChanged_Event_Handles_Multiple_Runnables_In_Session_Stack ()
  202. {
  203. // Arrange
  204. using IApplication app = Application.Create ();
  205. app.Init ("fake");
  206. using var runnable1 = new Runnable ();
  207. SessionToken? token1 = app.Begin (runnable1);
  208. app.LayoutAndDraw ();
  209. using var runnable2 = new Runnable ();
  210. SessionToken? token2 = app.Begin (runnable2);
  211. app.LayoutAndDraw ();
  212. // Both should not need layout after drawing
  213. Assert.False (runnable1.NeedsLayout);
  214. Assert.False (runnable2.NeedsLayout);
  215. try
  216. {
  217. // Act - Resize should mark both as needing layout
  218. app.Driver!.SetScreenSize (100, 30);
  219. // Assert
  220. Assert.True (runnable1.NeedsLayout);
  221. Assert.True (runnable2.NeedsLayout);
  222. }
  223. finally
  224. {
  225. // Cleanup
  226. if (token2 is { })
  227. {
  228. app.End (token2);
  229. }
  230. if (token1 is { })
  231. {
  232. app.End (token1);
  233. }
  234. }
  235. }
  236. [Fact]
  237. public void ScreenChanged_Event_With_No_Active_Runnables_Does_Not_Throw ()
  238. {
  239. // Arrange
  240. using IApplication app = Application.Create ();
  241. app.Init ("fake");
  242. var eventFired = false;
  243. EventHandler<EventArgs<Rectangle>> handler = (sender, args) => { eventFired = true; };
  244. app.ScreenChanged += handler;
  245. try
  246. {
  247. // Act - Resize with no runnables
  248. Exception? exception = Record.Exception (() => app.Driver!.SetScreenSize (100, 30));
  249. // Assert
  250. Assert.Null (exception);
  251. Assert.True (eventFired);
  252. }
  253. finally
  254. {
  255. app.ScreenChanged -= handler;
  256. }
  257. }
  258. #endregion ScreenChanged Event Tests
  259. #region Screen Property Tests
  260. [Fact]
  261. public void Screen_Property_Returns_Driver_Screen_When_Not_Set ()
  262. {
  263. // Arrange
  264. using IApplication app = Application.Create ();
  265. app.Init ("fake");
  266. // Act
  267. Rectangle screen = app.Screen;
  268. // Assert
  269. Assert.Equal (app.Driver!.Screen, screen);
  270. Assert.Equal (new (0, 0, 80, 25), screen);
  271. }
  272. [Fact]
  273. public void Screen_Property_Returns_Default_Size_When_Driver_Not_Initialized ()
  274. {
  275. // Arrange
  276. using IApplication app = Application.Create ();
  277. // Act - Don't call Init
  278. Rectangle screen = app.Screen;
  279. // Assert - Should return default size
  280. Assert.Equal (new (0, 0, 2048, 2048), screen);
  281. }
  282. [Fact]
  283. public void Screen_Property_Throws_When_Setting_Non_Zero_Origin ()
  284. {
  285. // Arrange
  286. using IApplication app = Application.Create ();
  287. app.Init ("fake");
  288. // Act & Assert
  289. var exception = Assert.Throws<NotImplementedException> (() =>
  290. app.Screen = new (10, 10, 80, 25));
  291. Assert.Contains ("Screen locations other than 0, 0", exception.Message);
  292. }
  293. [Fact]
  294. public void Screen_Property_Allows_Setting_With_Zero_Origin ()
  295. {
  296. // Arrange
  297. using IApplication app = Application.Create ();
  298. app.Init ("fake");
  299. // Act
  300. Exception? exception = Record.Exception (() =>
  301. app.Screen = new (0, 0, 100, 50));
  302. // Assert
  303. Assert.Null (exception);
  304. Assert.Equal (new (0, 0, 100, 50), app.Screen);
  305. }
  306. [Fact]
  307. public void Screen_Property_Setting_Raises_ScreenChanged_Event ()
  308. {
  309. // Arrange
  310. using IApplication app = Application.Create ();
  311. app.Init ("fake");
  312. var eventFired = false;
  313. EventHandler<EventArgs<Rectangle>> handler = (sender, args) => { eventFired = true; };
  314. app.ScreenChanged += handler;
  315. try
  316. {
  317. // Act - Manually set Screen property
  318. app.Screen = new (0, 0, 100, 50);
  319. Assert.True (eventFired);
  320. Assert.Equal (new (0, 0, 100, 50), app.Screen);
  321. }
  322. finally
  323. {
  324. app.ScreenChanged -= handler;
  325. }
  326. }
  327. [Fact]
  328. public void Screen_Property_Thread_Safe_Access ()
  329. {
  330. // Arrange
  331. using IApplication app = Application.Create ();
  332. app.Init ("fake");
  333. List<Exception> exceptions = new ();
  334. List<Task> tasks = new ();
  335. // Act - Access Screen property from multiple threads
  336. for (var i = 0; i < 10; i++)
  337. {
  338. tasks.Add (
  339. Task.Run (() =>
  340. {
  341. try
  342. {
  343. Rectangle screen = app.Screen;
  344. Assert.NotEqual (Rectangle.Empty, screen);
  345. }
  346. catch (Exception ex)
  347. {
  348. lock (exceptions)
  349. {
  350. exceptions.Add (ex);
  351. }
  352. }
  353. }));
  354. }
  355. #pragma warning disable xUnit1031
  356. Task.WaitAll (tasks.ToArray ());
  357. #pragma warning restore xUnit1031
  358. // Assert - No exceptions should occur
  359. Assert.Empty (exceptions);
  360. }
  361. #endregion Screen Property Tests
  362. }