Phase2RunnableMigrationTests.cs 9.7 KB

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