using Xunit.Abstractions;
namespace ApplicationTests.BeginEnd;
///
/// Comprehensive tests for ApplicationImpl.Begin/End logic that manages Current and SessionStack.
/// These tests ensure the fragile state management logic is robust and catches regressions.
/// Tests work directly with ApplicationImpl instances to avoid global Application state issues.
///
public class ApplicationImplBeginEndTests (ITestOutputHelper output)
{
private readonly ITestOutputHelper _output = output;
[Fact]
public void Init_Begin_End_Cleans_Up ()
{
IApplication? app = Application.Create ();
SessionToken? newSessionToken = null;
EventHandler newSessionTokenFn = (s, e) =>
{
Assert.NotNull (e.State);
newSessionToken = e.State;
};
app.SessionBegun += newSessionTokenFn;
Runnable runnable = new ();
SessionToken sessionToken = app.Begin (runnable)!;
Assert.NotNull (sessionToken);
Assert.NotNull (newSessionToken);
Assert.Equal (sessionToken, newSessionToken);
// Assert.Equal (runnable, Application.TopRunnable);
app.SessionBegun -= newSessionTokenFn;
app.End (newSessionToken);
Assert.Null (app.TopRunnable);
Assert.Null (app.Driver);
runnable.Dispose ();
}
[Fact]
public void Begin_Null_Runnable_Throws ()
{
IApplication app = Application.Create ();
app.Init ("fake");
// Test null Runnable
Assert.Throws (() => app.Begin (null!));
app.Dispose ();
}
[Fact]
public void Begin_Sets_Application_Top_To_Console_Size ()
{
IApplication app = Application.Create ();
app.Init ("fake");
Assert.Null (app.TopRunnableView);
app.Driver!.SetScreenSize (80, 25);
Runnable top = new ();
SessionToken? token = app.Begin (top);
Assert.Equal (new (0, 0, 80, 25), app.TopRunnableView!.Frame);
app.Driver!.SetScreenSize (5, 5);
app.LayoutAndDraw ();
Assert.Equal (new (0, 0, 5, 5), app.TopRunnableView!.Frame);
if (token is { })
{
app.End (token);
}
top.Dispose ();
app.Dispose ();
}
[Fact]
public void Begin_WithNullRunnable_ThrowsArgumentNullException ()
{
IApplication app = Application.Create ();
try
{
Assert.Throws (() => app.Begin (null!));
}
finally
{
app.Dispose ();
}
}
[Fact]
public void Begin_SetsCurrent_WhenCurrentIsNull ()
{
IApplication app = Application.Create ();
Runnable? runnable = null;
try
{
runnable = new ();
Assert.Null (app.TopRunnableView);
app.Begin (runnable);
Assert.NotNull (app.TopRunnableView);
Assert.Same (runnable, app.TopRunnableView);
Assert.Single (app.SessionStack!);
}
finally
{
runnable?.Dispose ();
app.Dispose ();
}
}
[Fact]
public void Begin_PushesToSessionStack ()
{
IApplication app = Application.Create ();
Runnable? runnable1 = null;
Runnable? runnable2 = null;
try
{
runnable1 = new () { Id = "1" };
runnable2 = new () { Id = "2" };
app.Begin (runnable1);
Assert.Single (app.SessionStack!);
Assert.Same (runnable1, app.TopRunnableView);
app.Begin (runnable2);
Assert.Equal (2, app.SessionStack!.Count);
Assert.Same (runnable2, app.TopRunnableView);
}
finally
{
runnable1?.Dispose ();
runnable2?.Dispose ();
app.Dispose ();
}
}
[Fact]
public void End_WithNullSessionToken_ThrowsArgumentNullException ()
{
IApplication app = Application.Create ();
try
{
Assert.Throws (() => app.End (null!));
}
finally
{
app.Dispose ();
}
}
[Fact]
public void End_PopsSessionStack ()
{
IApplication app = Application.Create ();
Runnable? runnable1 = null;
Runnable? runnable2 = null;
try
{
runnable1 = new () { Id = "1" };
runnable2 = new () { Id = "2" };
SessionToken token1 = app.Begin (runnable1)!;
SessionToken token2 = app.Begin (runnable2)!;
Assert.Equal (2, app.SessionStack!.Count);
app.End (token2);
Assert.Single (app.SessionStack!);
Assert.Same (runnable1, app.TopRunnableView);
app.End (token1);
Assert.Empty (app.SessionStack!);
}
finally
{
runnable1?.Dispose ();
runnable2?.Dispose ();
app.Dispose ();
}
}
[Fact (Skip = "This test may be bogus. What's wrong with ending a non-top session?")]
public void End_ThrowsArgumentException_WhenNotBalanced ()
{
IApplication app = Application.Create ();
Runnable? runnable1 = null;
Runnable? runnable2 = null;
try
{
runnable1 = new () { Id = "1" };
runnable2 = new () { Id = "2" };
SessionToken? token1 = app.Begin (runnable1);
SessionToken? token2 = app.Begin (runnable2);
// Trying to end token1 when token2 is on top should throw
// NOTE: This throws but has the side effect of popping token2 from the stack
Assert.Throws (() => app.End (token1!));
// Don't try to clean up with more End calls - the state is now inconsistent
// Let Shutdown/ResetState handle cleanup
}
finally
{
// Dispose runnables BEFORE Shutdown to satisfy DEBUG_IDISPOSABLE assertions
runnable1?.Dispose ();
runnable2?.Dispose ();
// Shutdown will call ResetState which clears any remaining state
app.Dispose ();
}
}
[Fact]
public void End_RestoresCurrentToPreviousRunnable ()
{
IApplication app = Application.Create ();
Runnable? runnable1 = null;
Runnable? runnable2 = null;
Runnable? runnable3 = null;
try
{
runnable1 = new () { Id = "1" };
runnable2 = new () { Id = "2" };
runnable3 = new () { Id = "3" };
SessionToken? token1 = app.Begin (runnable1);
SessionToken? token2 = app.Begin (runnable2);
SessionToken? token3 = app.Begin (runnable3);
Assert.Same (runnable3, app.TopRunnableView);
app.End (token3!);
Assert.Same (runnable2, app.TopRunnableView);
app.End (token2!);
Assert.Same (runnable1, app.TopRunnableView);
app.End (token1!);
}
finally
{
runnable1?.Dispose ();
runnable2?.Dispose ();
runnable3?.Dispose ();
app.Dispose ();
}
}
[Fact]
public void MultipleBeginEnd_MaintainsStackIntegrity ()
{
IApplication app = Application.Create ();
List runnables = new ();
List tokens = new ();
try
{
// Begin multiple runnables
for (var i = 0; i < 5; i++)
{
var runnable = new Runnable { Id = $"runnable-{i}" };
runnables.Add (runnable);
SessionToken? token = app.Begin (runnable);
tokens.Add (token!);
}
Assert.Equal (5, app.SessionStack!.Count);
Assert.Same (runnables [4], app.TopRunnableView);
// End them in reverse order (LIFO)
for (var i = 4; i >= 0; i--)
{
app.End (tokens [i]);
if (i > 0)
{
Assert.Equal (i, app.SessionStack.Count);
Assert.Same (runnables [i - 1], app.TopRunnableView);
}
else
{
Assert.Empty (app.SessionStack);
}
}
}
finally
{
foreach (Runnable runnable in runnables)
{
runnable.Dispose ();
}
app.Dispose ();
}
}
[Fact]
public void End_NullsSessionTokenRunnable ()
{
IApplication app = Application.Create ();
Runnable? runnable = null;
try
{
runnable = new ();
SessionToken? token = app.Begin (runnable);
Assert.Same (runnable, token!.Runnable);
app.End (token);
Assert.Null (token.Runnable);
}
finally
{
runnable?.Dispose ();
app.Dispose ();
}
}
[Fact]
public void ResetState_ClearsSessionStack ()
{
IApplication app = Application.Create ();
Runnable? runnable1 = null;
Runnable? runnable2 = null;
try
{
runnable1 = new () { Id = "1" };
runnable2 = new () { Id = "2" };
app.Begin (runnable1);
app.Begin (runnable2);
Assert.Equal (2, app.SessionStack!.Count);
Assert.NotNull (app.TopRunnableView);
}
finally
{
// Dispose runnables BEFORE Shutdown to satisfy DEBUG_IDISPOSABLE assertions
runnable1?.Dispose ();
runnable2?.Dispose ();
// Shutdown calls ResetState, which will clear SessionStack and set Current to null
app.Dispose ();
// Verify cleanup happened
Assert.Empty (app.SessionStack!);
Assert.Null (app.TopRunnableView);
}
}
[Fact]
public void ResetState_StopsAllRunningRunnables ()
{
IApplication app = Application.Create ();
Runnable? runnable1 = null;
Runnable? runnable2 = null;
try
{
runnable1 = new () { Id = "1" };
runnable2 = new () { Id = "2" };
app.Begin (runnable1);
app.Begin (runnable2);
Assert.True (runnable1.IsRunning);
Assert.True (runnable2.IsRunning);
}
finally
{
// Dispose runnables BEFORE Shutdown to satisfy DEBUG_IDISPOSABLE assertions
runnable1?.Dispose ();
runnable2?.Dispose ();
// Shutdown calls ResetState, which will stop all running runnables
app.Dispose ();
// Verify runnables were stopped
Assert.False (runnable1!.IsRunning);
Assert.False (runnable2!.IsRunning);
}
}
//[Fact]
//public void Begin_ActivatesNewRunnable_WhenCurrentExists ()
//{
// IApplication app = Application.Create ();
// Runnable? runnable1 = null;
// Runnable? runnable2 = null;
// try
// {
// runnable1 = new () { Id = "1" };
// runnable2 = new () { Id = "2" };
// var runnable1Deactivated = false;
// var runnable2Activated = false;
// runnable1.Deactivate += (s, e) => runnable1Deactivated = true;
// runnable2.Activate += (s, e) => runnable2Activated = true;
// app.Begin (runnable1);
// app.Begin (runnable2);
// Assert.True (runnable1Deactivated);
// Assert.True (runnable2Activated);
// Assert.Same (runnable2, app.TopRunnable);
// }
// finally
// {
// runnable1?.Dispose ();
// runnable2?.Dispose ();
// app.Dispose ();
// }
//}
[Fact]
public void SessionStack_ContainsAllBegunRunnables ()
{
IApplication app = Application.Create ();
List runnables = new ();
try
{
for (var i = 0; i < 10; i++)
{
var runnable = new Runnable { Id = $"runnable-{i}" };
runnables.Add (runnable);
app.Begin (runnable);
}
// All runnables should be in the stack
Assert.Equal (10, app.SessionStack!.Count);
// Verify stack contains all runnables
List stackList = app.SessionStack.ToList ();
foreach (Runnable runnable in runnables)
{
Assert.Contains (runnable, stackList.Select (r => r.Runnable));
}
}
finally
{
foreach (Runnable runnable in runnables)
{
runnable.Dispose ();
}
app.Dispose ();
}
}
}