Files
Terminal.Gui/Tests/UnitTestsParallelizable/Application/Runnable/RunnableEdgeCasesTests.cs
Tig 5da7e59aa2 Fixes #4456 - Clear MouseGrabView in App.End (#4460)
* Fixed MouseGrabView bug.

Added extensive test coverage for `Keyboard`, `Mouse`, `Timeout`, and `Popover` functionalities, including edge cases and concurrent access. Introduced parameterized and data-driven tests to reduce redundancy and improve clarity.

Refactored codebase for modularity and maintainability,
introducing new namespaces and reorganizing classes. Enhanced `MouseImpl`, `KeyboardImpl`, and `Runnable` implementations with improved event handling, thread safety, and support for the Terminal.Gui Cancellable Work Pattern (CWP).

Removed deprecated code and legacy tests, such as `LogarithmicTimeout` and `SmoothAcceleratingTimeout`. Fixed bugs related to mouse grabbing during drag operations and unbalanced `ApplicationImpl.Begin/End` calls. Improved documentation and code readability with modern C# features.

* Code cleanup.

* Update Tests/UnitTestsParallelizable/Application/Runnable/RunnableIntegrationTests.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Improve null handling and simplify test setup

In `MouseImpl.cs`, added an early `return` after the `UngrabMouse()`
call within the `if (view is null)` block to prevent further execution
when `view` is `null`, improving null reference handling.

In `RunnableIntegrationTests.cs`, removed the initialization of the
`IApplication` object (`app`) from the `MultipleRunnables_IndependentResults`
test method, simplifying the test setup and focusing on runnable behavior.

* Code cleanup

* API doc link cleanup

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-12-07 13:01:19 -07:00

294 lines
7.9 KiB
C#

using Xunit.Abstractions;
namespace ApplicationTests.RunnableTests;
/// <summary>
/// Tests for edge cases and error conditions in IRunnable implementation.
/// </summary>
public class RunnableEdgeCasesTests (ITestOutputHelper output)
{
private readonly ITestOutputHelper _output = output;
[Fact]
public void Runnable_MultipleEventSubscribers_AllInvoked ()
{
// Arrange
Runnable<int> runnable = new ();
var subscriber1Called = false;
var subscriber2Called = false;
var subscriber3Called = false;
runnable.IsRunningChanging += (s, e) => subscriber1Called = true;
runnable.IsRunningChanging += (s, e) => subscriber2Called = true;
runnable.IsRunningChanging += (s, e) => subscriber3Called = true;
// Act
runnable.RaiseIsRunningChanging (false, true);
// Assert
Assert.True (subscriber1Called);
Assert.True (subscriber2Called);
Assert.True (subscriber3Called);
}
[Fact]
public void Runnable_EventSubscriber_CanCancelAfterOthers ()
{
// Arrange
Runnable<int> runnable = new ();
var subscriber1Called = false;
var subscriber2Called = false;
runnable.IsRunningChanging += (s, e) => subscriber1Called = true;
runnable.IsRunningChanging += (s, e) =>
{
subscriber2Called = true;
e.Cancel = true; // Second subscriber cancels
};
// Act
bool canceled = runnable.RaiseIsRunningChanging (false, true);
// Assert
Assert.True (subscriber1Called);
Assert.True (subscriber2Called);
Assert.True (canceled);
}
[Fact]
public void Runnable_Result_CanBeSetMultipleTimes ()
{
// Arrange
Runnable<int> runnable = new ();
// Act
runnable.Result = 1;
runnable.Result = 2;
runnable.Result = 3;
// Assert
Assert.Equal (3, runnable.Result);
}
[Fact]
public void Runnable_Result_ClearedOnMultipleStarts ()
{
// Arrange
Runnable<int> runnable = new () { Result = 42 };
// Act & Assert - First start
runnable.RaiseIsRunningChanging (false, true);
Assert.Equal (0, runnable.Result);
// Set result again
runnable.Result = 99;
Assert.Equal (99, runnable.Result);
// Second start should clear again
runnable.RaiseIsRunningChanging (false, true);
Assert.Equal (0, runnable.Result);
}
[Fact]
public void Runnable_NullableResult_DefaultsToNull ()
{
// Arrange & Act
Runnable<string> runnable = new ();
// Assert
Assert.Null (runnable.Result);
}
[Fact]
public void Runnable_NullableResult_CanBeExplicitlyNull ()
{
// Arrange
Runnable<string> runnable = new () { Result = "test" };
// Act
runnable.Result = null;
// Assert
Assert.Null (runnable.Result);
}
[Fact]
public void Runnable_ComplexType_Result ()
{
// Arrange
Runnable<ComplexResult> runnable = new ();
ComplexResult result = new () { Value = 42, Text = "test" };
// Act
runnable.Result = result;
// Assert
Assert.NotNull (runnable.Result);
Assert.Equal (42, runnable.Result.Value);
Assert.Equal ("test", runnable.Result.Text);
}
[Fact]
public void Runnable_IsRunning_WithNoApp ()
{
// Arrange
Runnable<int> runnable = new ();
// Don't set App property
// Act & Assert
Assert.False (runnable.IsRunning);
}
[Fact]
public void Runnable_IsModal_WithNoApp ()
{
// Arrange
Runnable<int> runnable = new ();
// Don't set App property
// Act & Assert
Assert.False (runnable.IsModal);
}
[Fact]
public void Runnable_VirtualMethods_CanBeOverridden ()
{
// Arrange
OverriddenRunnable runnable = new ();
// Act
bool canceledRunning = runnable.RaiseIsRunningChanging (false, true);
runnable.RaiseIsRunningChangedEvent (true);
runnable.RaiseIsModalChangedEvent (true);
// Assert
Assert.True (runnable.OnIsRunningChangingCalled);
Assert.True (runnable.OnIsRunningChangedCalled);
Assert.True (runnable.OnIsModalChangedCalled);
}
[Fact]
public void Runnable_RequestStop_WithNoApp ()
{
// Arrange
Runnable<int> runnable = new ();
// Don't set App property
// Act & Assert - Should not throw
runnable.RequestStop ();
}
[Fact]
public void RunnableSessionToken_Constructor_RequiresRunnable ()
{
// This is implicitly tested by the constructor signature,
// but let's verify it creates with non-null runnable
// Arrange
Runnable<int> runnable = new ();
// Act
SessionToken token = new (runnable);
// Assert
Assert.NotNull (token.Runnable);
}
[Fact]
public void Runnable_EventArgs_PreservesValues ()
{
// Arrange
Runnable<int> runnable = new ();
bool? capturedOldValue = null;
bool? capturedNewValue = null;
runnable.IsRunningChanging += (s, e) =>
{
capturedOldValue = e.CurrentValue;
capturedNewValue = e.NewValue;
};
// Act
runnable.RaiseIsRunningChanging (false, true);
// Assert
Assert.NotNull (capturedOldValue);
Assert.NotNull (capturedNewValue);
Assert.False (capturedOldValue.Value);
Assert.True (capturedNewValue.Value);
}
[Fact]
public void Runnable_IsModalChanged_EventArgs_PreservesValue ()
{
// Arrange
Runnable<int> runnable = new ();
bool? capturedValue = null;
runnable.IsModalChanged += (s, e) => { capturedValue = e.Value; };
// Act
runnable.RaiseIsModalChangedEvent (true);
// Assert
Assert.NotNull (capturedValue);
Assert.True (capturedValue.Value);
}
[Fact]
public void Runnable_DifferentGenericTypes_Independent ()
{
// Arrange & Act
Runnable<int> intRunnable = new () { Result = 42 };
Runnable<string> stringRunnable = new () { Result = "test" };
Runnable<bool> boolRunnable = new () { Result = true };
// Assert
Assert.Equal (42, intRunnable.Result);
Assert.Equal ("test", stringRunnable.Result);
Assert.True (boolRunnable.Result);
}
/// <summary>
/// Complex result type for testing.
/// </summary>
private class ComplexResult
{
public int Value { get; set; }
public string? Text { get; set; }
}
/// <summary>
/// Runnable that tracks virtual method calls.
/// </summary>
private class OverriddenRunnable : Runnable<int>
{
public bool OnIsRunningChangingCalled { get; private set; }
public bool OnIsRunningChangedCalled { get; private set; }
public bool OnIsModalChangedCalled { get; private set; }
protected override bool OnIsRunningChanging (bool oldIsRunning, bool newIsRunning)
{
OnIsRunningChangingCalled = true;
return base.OnIsRunningChanging (oldIsRunning, newIsRunning);
}
protected override void OnIsRunningChanged (bool newIsRunning)
{
OnIsRunningChangedCalled = true;
base.OnIsRunningChanged (newIsRunning);
}
protected override void OnIsModalChanged (bool newIsModal)
{
OnIsModalChangedCalled = true;
base.OnIsModalChanged (newIsModal);
}
}
}