diff --git a/src/Spectre.Console.Testing/Cli/CommandAppTester.cs b/src/Spectre.Console.Testing/Cli/CommandAppTester.cs index d06ccd4..f53f60c 100644 --- a/src/Spectre.Console.Testing/Cli/CommandAppTester.cs +++ b/src/Spectre.Console.Testing/Cli/CommandAppTester.cs @@ -135,6 +135,52 @@ public sealed class CommandAppTester var result = app.Run(args); + var output = console.Output + .NormalizeLineEndings() + .TrimLines() + .Trim(); + + return new CommandAppResult(result, output, context, settings); + } + + /// + /// Runs the command application asynchronously. + /// + /// The arguments. + /// The result. + public async Task RunAsync(params string[] args) + { + var console = new TestConsole().Width(int.MaxValue); + return await RunAsync(args, console); + } + + private async Task RunAsync(string[] args, TestConsole console, Action? config = null) + { + CommandContext? context = null; + CommandSettings? settings = null; + + var app = new CommandApp(Registrar); + _appConfiguration?.Invoke(app); + + if (_configuration != null) + { + app.Configure(_configuration); + } + + if (config != null) + { + app.Configure(config); + } + + app.Configure(c => c.ConfigureConsole(console)); + app.Configure(c => c.SetInterceptor(new CallbackCommandInterceptor((ctx, s) => + { + context = ctx; + settings = s; + }))); + + var result = await app.RunAsync(args); + var output = console.Output .NormalizeLineEndings() .TrimLines() diff --git a/test/Spectre.Console.Cli.Tests/Data/Commands/AsynchronousCommand.cs b/test/Spectre.Console.Cli.Tests/Data/Commands/AsynchronousCommand.cs new file mode 100644 index 0000000..100d187 --- /dev/null +++ b/test/Spectre.Console.Cli.Tests/Data/Commands/AsynchronousCommand.cs @@ -0,0 +1,28 @@ +namespace Spectre.Console.Tests.Data; + +public sealed class AsynchronousCommand : AsyncCommand +{ + private readonly IAnsiConsole _console; + + public AsynchronousCommand(IAnsiConsole console) + { + _console = console; + } + + public async override Task ExecuteAsync(CommandContext context, AsynchronousCommandSettings settings) + { + // Simulate a long running asynchronous task + await Task.Delay(200); + + if (settings.ThrowException) + { + throw new Exception($"Throwing exception asynchronously"); + } + else + { + _console.WriteLine($"Finished executing asynchronously"); + } + + return 0; + } +} diff --git a/test/Spectre.Console.Cli.Tests/Data/Commands/ThrowingCommand.cs b/test/Spectre.Console.Cli.Tests/Data/Commands/ThrowingCommand.cs index 686bf4a..4804525 100644 --- a/test/Spectre.Console.Cli.Tests/Data/Commands/ThrowingCommand.cs +++ b/test/Spectre.Console.Cli.Tests/Data/Commands/ThrowingCommand.cs @@ -6,8 +6,4 @@ public sealed class ThrowingCommand : Command { throw new InvalidOperationException("W00t?"); } -} - -public sealed class ThrowingCommandSettings : CommandSettings -{ -} +} \ No newline at end of file diff --git a/test/Spectre.Console.Cli.Tests/Data/Settings/AsynchronousCommandSettings.cs b/test/Spectre.Console.Cli.Tests/Data/Settings/AsynchronousCommandSettings.cs new file mode 100644 index 0000000..b7bb8e2 --- /dev/null +++ b/test/Spectre.Console.Cli.Tests/Data/Settings/AsynchronousCommandSettings.cs @@ -0,0 +1,8 @@ +namespace Spectre.Console.Tests.Data; + +public sealed class AsynchronousCommandSettings : CommandSettings +{ + [CommandOption("--ThrowException")] + [DefaultValue(false)] + public bool ThrowException { get; set; } +} \ No newline at end of file diff --git a/test/Spectre.Console.Cli.Tests/Data/Settings/ThrowingCommandSettings.cs b/test/Spectre.Console.Cli.Tests/Data/Settings/ThrowingCommandSettings.cs new file mode 100644 index 0000000..88a28ff --- /dev/null +++ b/test/Spectre.Console.Cli.Tests/Data/Settings/ThrowingCommandSettings.cs @@ -0,0 +1,5 @@ +namespace Spectre.Console.Tests.Data; + +public sealed class ThrowingCommandSettings : CommandSettings +{ +} diff --git a/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Async.cs b/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Async.cs new file mode 100644 index 0000000..ee88b0b --- /dev/null +++ b/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Async.cs @@ -0,0 +1,70 @@ +namespace Spectre.Console.Tests.Unit.Cli; + +public sealed partial class CommandAppTests +{ + public sealed class Async + { + [Fact] + public async void Should_Execute_Command_Asynchronously() + { + // Given + var app = new CommandAppTester(); + app.SetDefaultCommand(); + app.Configure(config => + { + config.PropagateExceptions(); + }); + + // When + var result = await app.RunAsync(); + + // Then + result.ExitCode.ShouldBe(0); + result.Output.ShouldBe("Finished executing asynchronously"); + } + + [Fact] + public async void Should_Handle_Exception_Asynchronously() + { + // Given + var app = new CommandAppTester(); + app.SetDefaultCommand(); + + // When + var result = await app.RunAsync(new[] + { + "--ThrowException", + "true", + }); + + // Then + result.ExitCode.ShouldBe(-1); + } + + [Fact] + public async void Should_Throw_Exception_Asynchronously() + { + // Given + var app = new CommandAppTester(); + app.SetDefaultCommand(); + app.Configure(config => + { + config.PropagateExceptions(); + }); + + // When + var result = await Record.ExceptionAsync(async () => + await app.RunAsync(new[] + { + "--ThrowException", + "true", + })); + + // Then + result.ShouldBeOfType().And(ex => + { + ex.Message.ShouldBe("Throwing exception asynchronously"); + }); + } + } +} \ No newline at end of file