Phase2RunnableMigrationTests.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. using Xunit;
  2. using Terminal.Gui.App;
  3. using Terminal.Gui.ViewBase;
  4. using Terminal.Gui.Views;
  5. namespace Terminal.Gui.ViewTests;
  6. /// <summary>
  7. /// Tests for Phase 2 of the IRunnable migration: Toplevel, Dialog, MessageBox, and Wizard implementing IRunnable pattern.
  8. /// These tests verify that the migrated components work correctly with the new IRunnable architecture.
  9. /// </summary>
  10. public class Phase2RunnableMigrationTests
  11. {
  12. [Fact]
  13. public void Toplevel_ImplementsIRunnable()
  14. {
  15. // Arrange
  16. Toplevel toplevel = new ();
  17. // Act & Assert
  18. Assert.IsAssignableFrom<IRunnable> (toplevel);
  19. }
  20. [Fact]
  21. public void Dialog_ImplementsIRunnableInt()
  22. {
  23. // Arrange
  24. Dialog dialog = new ();
  25. // Act & Assert
  26. Assert.IsAssignableFrom<IRunnable<int?>> (dialog);
  27. }
  28. [Fact]
  29. public void Dialog_Result_DefaultsToNull()
  30. {
  31. // Arrange
  32. Dialog dialog = new ();
  33. // Act & Assert
  34. Assert.Null (dialog.Result);
  35. }
  36. [Fact]
  37. public void Dialog_Result_SetInOnIsRunningChanging()
  38. {
  39. // Arrange
  40. IApplication app = Application.Create ();
  41. app.Init ();
  42. Dialog dialog = new ()
  43. {
  44. Title = "Test Dialog",
  45. Buttons =
  46. [
  47. new Button { Text = "OK" },
  48. new Button { Text = "Cancel" }
  49. ]
  50. };
  51. int? extractedResult = null;
  52. // Subscribe to verify Result is set before IsRunningChanged fires
  53. ((IRunnable)dialog).IsRunningChanged += (s, e) =>
  54. {
  55. if (!e.Value) // Stopped
  56. {
  57. extractedResult = dialog.Result;
  58. }
  59. };
  60. // Act
  61. // Simulate clicking the first button (index 0)
  62. app.Run (dialog);
  63. dialog.Buttons [0].SetFocus ();
  64. app.RequestStop (dialog);
  65. // Assert
  66. Assert.NotNull (extractedResult);
  67. Assert.Equal (0, extractedResult);
  68. Assert.Equal (0, dialog.Result);
  69. dialog.Dispose ();
  70. app.Shutdown ();
  71. }
  72. [Fact]
  73. public void Dialog_Result_IsNullWhenCanceled()
  74. {
  75. // Arrange
  76. IApplication app = Application.Create ();
  77. app.Init ();
  78. Dialog dialog = new ()
  79. {
  80. Title = "Test Dialog",
  81. Buttons =
  82. [
  83. new Button { Text = "OK" }
  84. ]
  85. };
  86. // Act
  87. app.Run (dialog);
  88. // Don't focus any button - simulate cancel (ESC pressed)
  89. app.RequestStop (dialog);
  90. // Assert
  91. Assert.Null (dialog.Result);
  92. dialog.Dispose ();
  93. app.Shutdown ();
  94. }
  95. [Fact]
  96. public void Dialog_Canceled_PropertyMatchesResult()
  97. {
  98. // Arrange
  99. IApplication app = Application.Create ();
  100. app.Init ();
  101. Dialog dialog = new ()
  102. {
  103. Title = "Test Dialog",
  104. Buttons = [new Button { Text = "OK" }]
  105. };
  106. // Act - Cancel the dialog
  107. app.Run (dialog);
  108. app.RequestStop (dialog);
  109. // Assert
  110. Assert.True (dialog.Canceled);
  111. Assert.Null (dialog.Result);
  112. dialog.Dispose ();
  113. app.Shutdown ();
  114. }
  115. [Fact]
  116. public void MessageBox_Query_ReturnsDialogResult()
  117. {
  118. // Arrange
  119. IApplication app = Application.Create ();
  120. app.Init ();
  121. // Act
  122. // MessageBox.Query creates a Dialog internally and returns its Result
  123. // We can't easily test this without actually running the UI, but we can verify the pattern
  124. // Create a Dialog similar to what MessageBox creates
  125. Dialog dialog = new ()
  126. {
  127. Title = "Test",
  128. Text = "Message",
  129. Buttons =
  130. [
  131. new Button { Text = "Yes" },
  132. new Button { Text = "No" }
  133. ]
  134. };
  135. app.Run (dialog);
  136. dialog.Buttons [1].SetFocus (); // Focus "No" button (index 1)
  137. app.RequestStop (dialog);
  138. int result = dialog.Result ?? -1;
  139. // Assert
  140. Assert.Equal (1, result);
  141. Assert.Equal (1, dialog.Result);
  142. dialog.Dispose ();
  143. app.Shutdown ();
  144. }
  145. [Fact]
  146. public void MessageBox_Clicked_PropertyUpdated()
  147. {
  148. // Arrange & Act
  149. // MessageBox.Clicked is updated from Dialog.Result for backward compatibility
  150. // Since we can't easily run MessageBox.Query without UI, we verify the pattern is correct
  151. // The implementation should be:
  152. // int result = dialog.Result ?? -1;
  153. // MessageBox.Clicked = result;
  154. // Assert
  155. // This test verifies the property exists and has the expected type
  156. int clicked = MessageBox.Clicked;
  157. Assert.True (clicked is int);
  158. }
  159. [Fact]
  160. public void Wizard_InheritsFromDialog_ImplementsIRunnable()
  161. {
  162. // Arrange
  163. Wizard wizard = new ();
  164. // Act & Assert
  165. Assert.IsAssignableFrom<Dialog> (wizard);
  166. Assert.IsAssignableFrom<IRunnable<int?>> (wizard);
  167. }
  168. [Fact]
  169. public void Wizard_WasFinished_DefaultsToFalse()
  170. {
  171. // Arrange
  172. Wizard wizard = new ();
  173. // Act & Assert
  174. Assert.False (wizard.WasFinished);
  175. }
  176. [Fact]
  177. public void Wizard_WasFinished_TrueWhenFinished()
  178. {
  179. // Arrange
  180. IApplication app = Application.Create ();
  181. app.Init ();
  182. Wizard wizard = new ();
  183. WizardStep step = new ();
  184. step.Title = "Step 1";
  185. wizard.AddStep (step);
  186. bool finishedEventFired = false;
  187. wizard.Finished += (s, e) => { finishedEventFired = true; };
  188. // Act
  189. app.Run (wizard);
  190. wizard.CurrentStep = step;
  191. // Simulate finishing the wizard
  192. wizard.NextFinishButton.SetFocus ();
  193. app.RequestStop (wizard);
  194. // Assert
  195. Assert.True (finishedEventFired);
  196. // Note: WasFinished depends on internal _finishedPressed flag being set
  197. wizard.Dispose ();
  198. app.Shutdown ();
  199. }
  200. [Fact]
  201. public void Toplevel_Running_PropertyUpdatedByIRunnable()
  202. {
  203. // Arrange
  204. IApplication app = Application.Create ();
  205. app.Init ();
  206. Toplevel toplevel = new ();
  207. // Act
  208. app.Run (toplevel);
  209. bool runningWhileRunning = toplevel.Running;
  210. app.RequestStop (toplevel);
  211. bool runningAfterStop = toplevel.Running;
  212. // Assert
  213. Assert.True (runningWhileRunning);
  214. Assert.False (runningAfterStop);
  215. toplevel.Dispose ();
  216. app.Shutdown ();
  217. }
  218. [Fact]
  219. public void Toplevel_Modal_PropertyIndependentOfIRunnable()
  220. {
  221. // Arrange
  222. Toplevel toplevel = new ();
  223. // Act
  224. toplevel.Modal = true;
  225. bool modalValue = toplevel.Modal;
  226. // Assert
  227. Assert.True (modalValue);
  228. // Modal property is separate from IRunnable.IsModal
  229. // This test verifies the legacy Modal property still works
  230. }
  231. [Fact]
  232. public void Dialog_OnIsRunningChanging_CanCancelStopping()
  233. {
  234. // Arrange
  235. IApplication app = Application.Create ();
  236. app.Init ();
  237. TestDialog dialog = new ();
  238. dialog.CancelStopping = true;
  239. // Act
  240. app.Run (dialog);
  241. app.RequestStop (dialog);
  242. // The dialog should still be running because we canceled the stop
  243. bool stillRunning = ((IRunnable)dialog).IsRunning;
  244. // Clean up - force stop
  245. dialog.CancelStopping = false;
  246. app.RequestStop (dialog);
  247. // Assert
  248. Assert.True (stillRunning);
  249. dialog.Dispose ();
  250. app.Shutdown ();
  251. }
  252. [Fact]
  253. public void Dialog_IsRunningChanging_EventFires()
  254. {
  255. // Arrange
  256. IApplication app = Application.Create ();
  257. app.Init ();
  258. Dialog dialog = new ();
  259. int eventFireCount = 0;
  260. bool? lastNewValue = null;
  261. ((IRunnable)dialog).IsRunningChanging += (s, e) =>
  262. {
  263. eventFireCount++;
  264. lastNewValue = e.NewValue;
  265. };
  266. // Act
  267. app.Run (dialog);
  268. app.RequestStop (dialog);
  269. // Assert
  270. Assert.Equal (2, eventFireCount); // Once for starting, once for stopping
  271. Assert.False (lastNewValue); // Last event was for stopping (false)
  272. dialog.Dispose ();
  273. app.Shutdown ();
  274. }
  275. [Fact]
  276. public void Dialog_IsRunningChanged_EventFires()
  277. {
  278. // Arrange
  279. IApplication app = Application.Create ();
  280. app.Init ();
  281. Dialog dialog = new ();
  282. int eventFireCount = 0;
  283. bool? lastValue = null;
  284. ((IRunnable)dialog).IsRunningChanged += (s, e) =>
  285. {
  286. eventFireCount++;
  287. lastValue = e.Value;
  288. };
  289. // Act
  290. app.Run (dialog);
  291. app.RequestStop (dialog);
  292. // Assert
  293. Assert.Equal (2, eventFireCount); // Once for started, once for stopped
  294. Assert.False (lastValue); // Last event was for stopped (false)
  295. dialog.Dispose ();
  296. app.Shutdown ();
  297. }
  298. /// <summary>
  299. /// Test helper dialog that can cancel stopping
  300. /// </summary>
  301. private class TestDialog : Dialog
  302. {
  303. public bool CancelStopping { get; set; }
  304. protected override bool OnIsRunningChanging (bool oldIsRunning, bool newIsRunning)
  305. {
  306. if (!newIsRunning && CancelStopping)
  307. {
  308. return true; // Cancel stopping
  309. }
  310. return base.OnIsRunningChanging (oldIsRunning, newIsRunning);
  311. }
  312. }
  313. }