Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Terminal.Gui/App/ApplicationImpl.Run.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ internal partial class ApplicationImpl
/// <inheritdoc/>
public void Invoke (Action<IApplication>? action)
{
if (!Initialized)
{
throw new NotInitializedException (nameof (Invoke));
}

// If we are already on the main UI thread
if (TopRunnableView is IRunnable { IsRunning: true } && MainThreadId == Thread.CurrentThread.ManagedThreadId)
{
Expand All @@ -75,6 +80,13 @@ public void Invoke (Action<IApplication>? action)
/// <inheritdoc/>
public void Invoke (Action action)
{
ArgumentNullException.ThrowIfNull (action);

if (!Initialized)
{
throw new NotInitializedException (nameof (Invoke));
}
Comment thread
tig marked this conversation as resolved.
Comment thread
tig marked this conversation as resolved.

// If we are already on the main UI thread
if (TopRunnableView is IRunnable { IsRunning: true } && MainThreadId == Thread.CurrentThread.ManagedThreadId)
{
Expand Down
6 changes: 6 additions & 0 deletions Terminal.Gui/App/IApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,9 @@ public interface IApplication : IDisposable
/// iteration.
/// </para>
/// </remarks>
/// <exception cref="NotInitializedException">
/// Thrown when <see cref="Init"/> has not been called or after <see cref="IDisposable.Dispose"/> has been called.
/// </exception>
void Invoke (Action<IApplication>? action);

/// <summary>Runs <paramref name="action"/> on the main UI loop thread.</summary>
Expand All @@ -365,6 +368,9 @@ public interface IApplication : IDisposable
/// iteration.
/// </para>
/// </remarks>
/// <exception cref="NotInitializedException">
/// Thrown when <see cref="Init"/> has not been called or after <see cref="IDisposable.Dispose"/> has been called.
/// </exception>
void Invoke (Action action);

#endregion Iteration & Invoke
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Claude - Opus 4.7
Comment thread
tig marked this conversation as resolved.
#nullable enable

namespace UnitTests.NonParallelizable.ApplicationTests;

/// <summary>
/// Tests asserting <see cref="IApplication.Invoke(Action)"/> and
/// <see cref="IApplication.Invoke(System.Action{IApplication})"/> contract relative to the
/// <see cref="IApplication.Init"/> / <see cref="IDisposable.Dispose"/> lifecycle.
/// Regression coverage for issue #5163.
/// </summary>
public class ApplicationInvokeLifecycleTests
{
[Fact]
public void Invoke_Action_BeforeInit_Throws_NotInitializedException ()
{
IApplication app = Application.Create ();

try
{
Assert.Throws<NotInitializedException> (() => app.Invoke (() => { }));
}
finally
{
app.Dispose ();
}
}

[Fact]
public void Invoke_ActionOfApplication_BeforeInit_Throws_NotInitializedException ()
{
IApplication app = Application.Create ();

try
{
Assert.Throws<NotInitializedException> (() => app.Invoke (static _ => { }));
}
finally
{
app.Dispose ();
}
}

[Fact]
public void Invoke_Action_AfterDispose_Throws_NotInitializedException ()
{
IApplication app = Application.Create ();

try
{
app.Init (DriverRegistry.Names.ANSI);
}
finally
{
app.Dispose ();
}

Assert.Throws<NotInitializedException> (() => app.Invoke (() => { }));
}

[Fact]
public void Invoke_ActionOfApplication_AfterDispose_Throws_NotInitializedException ()
{
IApplication app = Application.Create ();

try
{
app.Init (DriverRegistry.Names.ANSI);
}
finally
{
app.Dispose ();
}

Assert.Throws<NotInitializedException> (() => app.Invoke (static _ => { }));
}
}
Loading