RunnableEdgeCasesTests.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. #nullable enable
  2. using Xunit.Abstractions;
  3. namespace ApplicationTests;
  4. /// <summary>
  5. /// Tests for edge cases and error conditions in IRunnable implementation.
  6. /// </summary>
  7. public class RunnableEdgeCasesTests (ITestOutputHelper output)
  8. {
  9. private readonly ITestOutputHelper _output = output;
  10. [Fact]
  11. public void Runnable_MultipleEventSubscribers_AllInvoked ()
  12. {
  13. // Arrange
  14. Runnable<int> runnable = new ();
  15. var subscriber1Called = false;
  16. var subscriber2Called = false;
  17. var subscriber3Called = false;
  18. runnable.IsRunningChanging += (s, e) => subscriber1Called = true;
  19. runnable.IsRunningChanging += (s, e) => subscriber2Called = true;
  20. runnable.IsRunningChanging += (s, e) => subscriber3Called = true;
  21. // Act
  22. runnable.RaiseIsRunningChanging (false, true);
  23. // Assert
  24. Assert.True (subscriber1Called);
  25. Assert.True (subscriber2Called);
  26. Assert.True (subscriber3Called);
  27. }
  28. [Fact]
  29. public void Runnable_EventSubscriber_CanCancelAfterOthers ()
  30. {
  31. // Arrange
  32. Runnable<int> runnable = new ();
  33. var subscriber1Called = false;
  34. var subscriber2Called = false;
  35. runnable.IsRunningChanging += (s, e) => subscriber1Called = true;
  36. runnable.IsRunningChanging += (s, e) =>
  37. {
  38. subscriber2Called = true;
  39. e.Cancel = true; // Second subscriber cancels
  40. };
  41. // Act
  42. bool canceled = runnable.RaiseIsRunningChanging (false, true);
  43. // Assert
  44. Assert.True (subscriber1Called);
  45. Assert.True (subscriber2Called);
  46. Assert.True (canceled);
  47. }
  48. [Fact]
  49. public void Runnable_Result_CanBeSetMultipleTimes ()
  50. {
  51. // Arrange
  52. Runnable<int> runnable = new ();
  53. // Act
  54. runnable.Result = 1;
  55. runnable.Result = 2;
  56. runnable.Result = 3;
  57. // Assert
  58. Assert.Equal (3, runnable.Result);
  59. }
  60. [Fact]
  61. public void Runnable_Result_ClearedOnMultipleStarts ()
  62. {
  63. // Arrange
  64. Runnable<int> runnable = new () { Result = 42 };
  65. // Act & Assert - First start
  66. runnable.RaiseIsRunningChanging (false, true);
  67. Assert.Equal (0, runnable.Result);
  68. // Set result again
  69. runnable.Result = 99;
  70. Assert.Equal (99, runnable.Result);
  71. // Second start should clear again
  72. runnable.RaiseIsRunningChanging (false, true);
  73. Assert.Equal (0, runnable.Result);
  74. }
  75. [Fact]
  76. public void Runnable_NullableResult_DefaultsToNull ()
  77. {
  78. // Arrange & Act
  79. Runnable<string> runnable = new ();
  80. // Assert
  81. Assert.Null (runnable.Result);
  82. }
  83. [Fact]
  84. public void Runnable_NullableResult_CanBeExplicitlyNull ()
  85. {
  86. // Arrange
  87. Runnable<string> runnable = new () { Result = "test" };
  88. // Act
  89. runnable.Result = null;
  90. // Assert
  91. Assert.Null (runnable.Result);
  92. }
  93. [Fact]
  94. public void Runnable_ComplexType_Result ()
  95. {
  96. // Arrange
  97. Runnable<ComplexResult> runnable = new ();
  98. ComplexResult result = new () { Value = 42, Text = "test" };
  99. // Act
  100. runnable.Result = result;
  101. // Assert
  102. Assert.NotNull (runnable.Result);
  103. Assert.Equal (42, runnable.Result.Value);
  104. Assert.Equal ("test", runnable.Result.Text);
  105. }
  106. [Fact]
  107. public void Runnable_IsRunning_WithNoApp ()
  108. {
  109. // Arrange
  110. Runnable<int> runnable = new ();
  111. // Don't set App property
  112. // Act & Assert
  113. Assert.False (runnable.IsRunning);
  114. }
  115. [Fact]
  116. public void Runnable_IsModal_WithNoApp ()
  117. {
  118. // Arrange
  119. Runnable<int> runnable = new ();
  120. // Don't set App property
  121. // Act & Assert
  122. Assert.False (runnable.IsModal);
  123. }
  124. [Fact]
  125. public void Runnable_VirtualMethods_CanBeOverridden ()
  126. {
  127. // Arrange
  128. OverriddenRunnable runnable = new ();
  129. // Act
  130. bool canceledRunning = runnable.RaiseIsRunningChanging (false, true);
  131. runnable.RaiseIsRunningChangedEvent (true);
  132. runnable.RaiseIsModalChangedEvent (true);
  133. // Assert
  134. Assert.True (runnable.OnIsRunningChangingCalled);
  135. Assert.True (runnable.OnIsRunningChangedCalled);
  136. Assert.True (runnable.OnIsModalChangedCalled);
  137. }
  138. [Fact]
  139. public void Runnable_RequestStop_WithNoApp ()
  140. {
  141. // Arrange
  142. Runnable<int> runnable = new ();
  143. // Don't set App property
  144. // Act & Assert - Should not throw
  145. runnable.RequestStop ();
  146. }
  147. [Fact]
  148. public void RunnableSessionToken_Constructor_RequiresRunnable ()
  149. {
  150. // This is implicitly tested by the constructor signature,
  151. // but let's verify it creates with non-null runnable
  152. // Arrange
  153. Runnable<int> runnable = new ();
  154. // Act
  155. SessionToken token = new (runnable);
  156. // Assert
  157. Assert.NotNull (token.Runnable);
  158. }
  159. [Fact]
  160. public void Runnable_EventArgs_PreservesValues ()
  161. {
  162. // Arrange
  163. Runnable<int> runnable = new ();
  164. bool? capturedOldValue = null;
  165. bool? capturedNewValue = null;
  166. runnable.IsRunningChanging += (s, e) =>
  167. {
  168. capturedOldValue = e.CurrentValue;
  169. capturedNewValue = e.NewValue;
  170. };
  171. // Act
  172. runnable.RaiseIsRunningChanging (false, true);
  173. // Assert
  174. Assert.NotNull (capturedOldValue);
  175. Assert.NotNull (capturedNewValue);
  176. Assert.False (capturedOldValue.Value);
  177. Assert.True (capturedNewValue.Value);
  178. }
  179. [Fact]
  180. public void Runnable_IsModalChanged_EventArgs_PreservesValue ()
  181. {
  182. // Arrange
  183. Runnable<int> runnable = new ();
  184. bool? capturedValue = null;
  185. runnable.IsModalChanged += (s, e) => { capturedValue = e.Value; };
  186. // Act
  187. runnable.RaiseIsModalChangedEvent (true);
  188. // Assert
  189. Assert.NotNull (capturedValue);
  190. Assert.True (capturedValue.Value);
  191. }
  192. [Fact]
  193. public void Runnable_DifferentGenericTypes_Independent ()
  194. {
  195. // Arrange & Act
  196. Runnable<int> intRunnable = new () { Result = 42 };
  197. Runnable<string> stringRunnable = new () { Result = "test" };
  198. Runnable<bool> boolRunnable = new () { Result = true };
  199. // Assert
  200. Assert.Equal (42, intRunnable.Result);
  201. Assert.Equal ("test", stringRunnable.Result);
  202. Assert.True (boolRunnable.Result);
  203. }
  204. /// <summary>
  205. /// Complex result type for testing.
  206. /// </summary>
  207. private class ComplexResult
  208. {
  209. public int Value { get; set; }
  210. public string? Text { get; set; }
  211. }
  212. /// <summary>
  213. /// Runnable that tracks virtual method calls.
  214. /// </summary>
  215. private class OverriddenRunnable : Runnable<int>
  216. {
  217. public bool OnIsRunningChangingCalled { get; private set; }
  218. public bool OnIsRunningChangedCalled { get; private set; }
  219. public bool OnIsModalChangedCalled { get; private set; }
  220. protected override bool OnIsRunningChanging (bool oldIsRunning, bool newIsRunning)
  221. {
  222. OnIsRunningChangingCalled = true;
  223. return base.OnIsRunningChanging (oldIsRunning, newIsRunning);
  224. }
  225. protected override void OnIsRunningChanged (bool newIsRunning)
  226. {
  227. OnIsRunningChangedCalled = true;
  228. base.OnIsRunningChanged (newIsRunning);
  229. }
  230. protected override void OnIsModalChanged (bool newIsModal)
  231. {
  232. OnIsModalChangedCalled = true;
  233. base.OnIsModalChanged (newIsModal);
  234. }
  235. }
  236. }