#nullable enable using Xunit.Abstractions; namespace ApplicationTests.RunnableTests; /// /// Integration tests for IApplication's IRunnable support. /// Tests the full lifecycle of IRunnable instances through Application methods. /// public class ApplicationRunnableIntegrationTests { [Fact] public void Begin_AddsRunnableToStack () { // Arrange IApplication app = CreateAndInitApp (); Runnable runnable = new (); int stackCountBefore = app.SessionStack?.Count ?? 0; // Act SessionToken? token = app.Begin (runnable); // Assert Assert.NotNull (token); Assert.NotNull (token.Runnable); Assert.Same (runnable, token.Runnable); Assert.Equal (stackCountBefore + 1, app.SessionStack?.Count ?? 0); // Cleanup app.End (token!); } [Fact] public void Begin_CanBeCanceled_ByIsRunningChanging () { // Arrange IApplication app = CreateAndInitApp (); CancelableRunnable runnable = new () { CancelStart = true }; // Act SessionToken? token = app.Begin (runnable); // Assert - Should not be added to stack if canceled Assert.False (runnable.IsRunning); // Token not created Assert.Null (token); } [Fact] public void Begin_RaisesIsModalChangedEvent () { // Arrange IApplication app = CreateAndInitApp (); Runnable runnable = new (); var isModalChangedRaised = false; bool? receivedValue = null; runnable.IsModalChanged += (s, e) => { isModalChangedRaised = true; receivedValue = e.Value; }; // Act SessionToken? token = app.Begin (runnable); // Assert Assert.True (isModalChangedRaised); Assert.True (receivedValue); // Cleanup app.End (token!); } [Fact] public void Begin_RaisesIsRunningChangedEvent () { // Arrange IApplication app = CreateAndInitApp (); Runnable runnable = new (); var isRunningChangedRaised = false; bool? receivedValue = null; runnable.IsRunningChanged += (s, e) => { isRunningChangedRaised = true; receivedValue = e.Value; }; // Act SessionToken? token = app.Begin (runnable); // Assert Assert.True (isRunningChangedRaised); Assert.True (receivedValue); // Cleanup app.End (token!); } [Fact] public void Begin_RaisesIsRunningChangingEvent () { // Arrange IApplication app = CreateAndInitApp (); Runnable runnable = new (); var isRunningChangingRaised = false; bool? oldValue = null; bool? newValue = null; runnable.IsRunningChanging += (s, e) => { isRunningChangingRaised = true; oldValue = e.CurrentValue; newValue = e.NewValue; }; // Act SessionToken? token = app.Begin (runnable); // Assert Assert.True (isRunningChangingRaised); Assert.False (oldValue); Assert.True (newValue); // Cleanup app.End (token!); } [Fact] public void Begin_SetsIsModalToTrue () { // Arrange IApplication app = CreateAndInitApp (); Runnable runnable = new (); // Act SessionToken? token = app.Begin (runnable); // Assert Assert.True (runnable.IsModal); // Cleanup app.End (token!); } [Fact] public void Begin_SetsIsRunningToTrue () { // Arrange IApplication app = CreateAndInitApp (); Runnable runnable = new (); // Act SessionToken? token = app.Begin (runnable); // Assert Assert.True (runnable.IsRunning); // Cleanup app.End (token!); } [Fact] public void Begin_ThrowsOnNullRunnable () { // Arrange IApplication app = CreateAndInitApp (); // Act & Assert Assert.Throws (() => app.Begin ((IRunnable)null!)); } [Fact] public void End_CanBeCanceled_ByIsRunningChanging () { // Arrange IApplication app = CreateAndInitApp (); CancelableRunnable runnable = new () { CancelStop = true }; SessionToken? token = app.Begin (runnable); runnable.CancelStop = true; // Enable cancellation // Act app.End (token!); // Assert - Should still be running if canceled Assert.True (runnable.IsRunning); // Force end by disabling cancellation runnable.CancelStop = false; app.End (token!); } [Fact] public void End_ClearsTokenRunnable () { // Arrange IApplication app = CreateAndInitApp (); Runnable runnable = new (); SessionToken? token = app.Begin (runnable); // Act app.End (token!); // Assert Assert.Null (token!.Runnable); } [Fact] public void End_RaisesIsRunningChangedEvent () { // Arrange IApplication app = CreateAndInitApp (); Runnable runnable = new (); SessionToken? token = app.Begin (runnable); var isRunningChangedRaised = false; bool? receivedValue = null; runnable.IsRunningChanged += (s, e) => { isRunningChangedRaised = true; receivedValue = e.Value; }; // Act app.End (token!); // Assert Assert.True (isRunningChangedRaised); Assert.False (receivedValue); } [Fact] public void End_RaisesIsRunningChangingEvent () { // Arrange IApplication app = CreateAndInitApp (); Runnable runnable = new (); SessionToken? token = app.Begin (runnable); var isRunningChangingRaised = false; bool? oldValue = null; bool? newValue = null; runnable.IsRunningChanging += (s, e) => { isRunningChangingRaised = true; oldValue = e.CurrentValue; newValue = e.NewValue; }; // Act app.End (token!); // Assert Assert.True (isRunningChangingRaised); Assert.True (oldValue); Assert.False (newValue); } [Fact] public void End_RemovesRunnableFromStack () { // Arrange IApplication app = CreateAndInitApp (); Runnable runnable = new (); SessionToken? token = app.Begin (runnable); int stackCountBefore = app.SessionStack?.Count ?? 0; // Act app.End (token!); // Assert Assert.Equal (stackCountBefore - 1, app.SessionStack?.Count ?? 0); } [Fact] public void End_SetsIsModalToFalse () { // Arrange IApplication app = CreateAndInitApp (); Runnable runnable = new (); SessionToken? token = app.Begin (runnable); // Act app.End (token!); // Assert Assert.False (runnable.IsModal); } [Fact] public void End_SetsIsRunningToFalse () { // Arrange IApplication app = CreateAndInitApp (); Runnable runnable = new (); SessionToken? token = app.Begin (runnable); // Act app.End (token!); // Assert Assert.False (runnable.IsRunning); } [Fact] public void End_ThrowsOnNullToken () { // Arrange IApplication app = CreateAndInitApp (); // Act & Assert Assert.Throws (() => app.End ((SessionToken)null!)); } [Fact] public void End_ClearsMouseGrabView () { // Arrange IApplication app = CreateAndInitApp (); Runnable runnable = new (); SessionToken? token = app.Begin (runnable); app.Mouse.GrabMouse (runnable); app.End (token!); Assert.Null (app.Mouse.MouseGrabView); runnable.Dispose (); app.Dispose (); } [Fact] public void MultipleRunnables_IndependentResults () { // Arrange Runnable runnable1 = new (); Runnable runnable2 = new (); // Act runnable1.Result = 42; runnable2.Result = "test"; // Assert Assert.Equal (42, runnable1.Result); Assert.Equal ("test", runnable2.Result); } [Fact] public void NestedBegin_MaintainsStackOrder () { // Arrange IApplication app = CreateAndInitApp (); Runnable runnable1 = new () { Id = "1" }; Runnable runnable2 = new () { Id = "2" }; // Act SessionToken token1 = app.Begin (runnable1)!; SessionToken token2 = app.Begin (runnable2)!; // Assert - runnable2 should be on top Assert.True (runnable2.IsModal); Assert.False (runnable1.IsModal); Assert.True (runnable1.IsRunning); // Still running, just not modal Assert.True (runnable2.IsRunning); // Cleanup app.End (token2); app.End (token1); } [Fact] public void NestedEnd_RestoresPreviousModal () { // Arrange IApplication app = CreateAndInitApp (); Runnable runnable1 = new () { Id = "1" }; Runnable runnable2 = new () { Id = "2" }; SessionToken token1 = app.Begin (runnable1)!; SessionToken token2 = app.Begin (runnable2)!; // Act - End the top runnable app.End (token2); // Assert - runnable1 should become modal again Assert.True (runnable1.IsModal); Assert.False (runnable2.IsModal); Assert.True (runnable1.IsRunning); Assert.False (runnable2.IsRunning); // Cleanup app.End (token1); } [Fact] public void RequestStop_WithIRunnable_WorksCorrectly () { // Arrange IApplication app = CreateAndInitApp (); StoppableRunnable runnable = new (); SessionToken? token = app.Begin (runnable); // Act app.RequestStop (runnable); // Assert - RequestStop should trigger End eventually // For now, just verify it doesn't throw Assert.NotNull (runnable); // Cleanup app.End (token!); } [Fact] public void RequestStop_WithNull_UsesTopRunnable () { // Arrange IApplication app = CreateAndInitApp (); StoppableRunnable runnable = new (); SessionToken? token = app.Begin (runnable); // Act app.RequestStop ((IRunnable?)null); // Assert - Should not throw Assert.NotNull (runnable); // Cleanup app.End (token!); } [Fact (Skip = "Run methods with main loop are not suitable for parallel tests - use non-parallel UnitTests instead")] public void RunGeneric_CreatesAndReturnsRunnable () { // Arrange IApplication app = CreateAndInitApp (); app.StopAfterFirstIteration = true; // Act - With fluent API, Run() returns IApplication for chaining IApplication result = app.Run (); // Assert Assert.NotNull (result); Assert.Same (app, result); // Fluent API returns this // Note: Run blocks until stopped, but StopAfterFirstIteration makes it return immediately // The runnable is automatically disposed by Dispose() } [Fact (Skip = "Run methods with main loop are not suitable for parallel tests - use non-parallel UnitTests instead")] public void RunGeneric_ThrowsIfNotInitialized () { // Arrange IApplication app = Application.Create (); // Don't call Init // Act & Assert Assert.Throws (() => app.Run ()); // Cleanup app.Dispose (); } private IApplication CreateAndInitApp () { IApplication app = Application.Create (); app.Init ("fake"); return app; } /// /// Test runnable that can cancel lifecycle changes. /// private class CancelableRunnable : Runnable { public bool CancelStart { get; set; } public bool CancelStop { get; set; } protected override bool OnIsRunningChanging (bool oldIsRunning, bool newIsRunning) { if (newIsRunning && CancelStart) { return true; // Cancel starting } if (!newIsRunning && CancelStop) { return true; // Cancel stopping } return base.OnIsRunningChanging (oldIsRunning, newIsRunning); } } /// /// Test runnable that can be stopped. /// private class StoppableRunnable : Runnable { public override void RequestStop () { WasStopRequested = true; base.RequestStop (); } public bool WasStopRequested { get; private set; } } /// /// Test runnable for generic Run tests. /// private class TestRunnable : Runnable { public TestRunnable () { Id = "TestRunnable"; } } }