Skip to content

Commit

Permalink
[feature] add ability to test command completion (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
kbond authored Oct 6, 2022
1 parent 686fbd2 commit d259ce4
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 1 deletion.
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,15 @@ class CreateUserCommandTest extends KernelTestCase
->assertOutputContains('Could not create user!') // can still make assertions on output before exception was thrown
;

// test completion
$this->consoleCommand('create:user')
->complete('')
->is(['kevin', 'john', 'jane'])
->contains('kevin') // chain assertions
->back() // fluently go back to the TestCommand
->complete('kevin --role=')->is(['ROLE_EMPLOYEE', 'ROLE_MANAGER'])
;

// access result
$result = $this->executeConsoleCommand('create:user');

Expand Down Expand Up @@ -143,6 +152,15 @@ class CreateUserCommandTest extends TestCase
->assertOutputContains('Could not create user!') // can still make assertions on output before exception was thrown
;

// test completion
TestCommand::for(new CreateUserCommand(/** args... */))
->complete('')
->is(['kevin', 'john', 'jane'])
->contains('kevin') // chain assertions
->back() // fluently go back to the TestCommand
->complete('kevin --role=')->is(['ROLE_EMPLOYEE', 'ROLE_MANAGER'])
;

// access result
$result = TestCommand::for(new CreateUserCommand(/** args... */))->execute();

Expand Down
1 change: 0 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ parameters:
level: 8
paths:
- src
- tests
41 changes: 41 additions & 0 deletions src/Assert/CompletionExpectation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Zenstruck\Console\Test\Assert;

use Zenstruck\Assert\Expectation;
use Zenstruck\Console\Test\TestCommand;

/**
* @author Kevin Bond <kevinbond@gmail.com>
*
* @mixin Expectation
*/
final class CompletionExpectation
{
private TestCommand $command;
private Expectation $expectation;

/**
* @internal
*/
public function __construct(TestCommand $command, Expectation $expectation)
{
$this->command = $command;
$this->expectation = $expectation;
}

/**
* @internal
*/
public function __call(string $name, array $arguments): self // @phpstan-ignore-line
{
$this->expectation->{$name}(...$arguments);

return $this;
}

public function back(): TestCommand
{
return $this->command;
}
}
12 changes: 12 additions & 0 deletions src/TestCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Tester\CommandCompletionTester;
use Zenstruck\Assert;
use Zenstruck\Console\Test\Assert\CompletionExpectation;

/**
* @author Kevin Bond <kevinbond@gmail.com>
Expand All @@ -13,6 +15,7 @@ final class TestCommand
{
private Application $application;
private string $cli;
private Command $command;

/** @var string[] */
private array $inputs = [];
Expand All @@ -33,6 +36,7 @@ private function __construct(Command $command, string $cli)

$this->application = $application;
$this->cli = $cli;
$this->command = $command;
}

public static function for(Command $command): self
Expand Down Expand Up @@ -140,6 +144,14 @@ public function execute(?string $cli = null): CommandResult
return new CommandResult($cli, $status, $output);
}

public function complete(string $cli): CompletionExpectation
{
return new CompletionExpectation(
$this,
Assert::that((new CommandCompletionTester($this->command))->complete(\explode(' ', $cli)))
);
}

private function doRun(TestInput $input, TestOutput $output): int
{
$fn = fn() => $this->application->run($input, $output);
Expand Down
90 changes: 90 additions & 0 deletions tests/UnitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
namespace Zenstruck\Console\Test\Tests;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Completion\CompletionInput;
use Symfony\Component\Console\Completion\CompletionSuggestions;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\HttpKernel\Kernel;
use Zenstruck\Console\Test\TestCommand;
use Zenstruck\Console\Test\Tests\Fixture\FixtureCommand;

Expand Down Expand Up @@ -193,4 +198,89 @@ public function always_executes_with_console_output(): void
->assertOutputContains('table row 2')
;
}

/**
* @test
*/
public function completion(): void
{
if (!\class_exists(CompletionInput::class)) {
$this->markTestSkipped('Command completion not available.');
}

$command = TestCommand::for(new class() extends Command {
public function getName(): string
{
return 'my:command';
}

public function configure(): void
{
$this
->addArgument('user')
->addOption('message', null, InputOption::VALUE_REQUIRED)
;
}

public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestArgumentValuesFor('user')) {
$suggestions->suggestValues(['kevin', 'john', 'jane']);
}

if ($input->mustSuggestOptionValuesFor('message')) {
$suggestions->suggestValues(['hello', 'hi', 'greetings']);
}
}
});

$command
->complete('')
->is(['kevin', 'john', 'jane'])
->contains('kevin')
->back()
->complete('ke')->is(['kevin', 'john', 'jane'])->back()
->complete('kevin --message=')->is(['hello', 'hi', 'greetings'])->back()
->complete('kevin --message=g')->is(['hello', 'hi', 'greetings'])->back()
;
}

/**
* @test
*/
public function completion_61(): void
{
if (!\class_exists(CompletionInput::class)) {
$this->markTestSkipped('Command completion not available.');
}

if (Kernel::VERSION_ID < 60100) {
$this->markTestSkipped('Using InputArgument/Option for defining suggestions requires 6.1+.');
}

$command = TestCommand::for(new class() extends Command {
public function getName(): string
{
return 'my:command';
}

public function configure(): void
{
$this
->addArgument('user', null, '', null, ['kevin', 'john', 'jane'])
->addOption('message', null, InputOption::VALUE_REQUIRED, '', null, ['hello', 'hi', 'greetings'])
;
}
});

$command
->complete('')
->is(['kevin', 'john', 'jane'])
->contains('kevin')
->back()
->complete('ke')->is(['kevin', 'john', 'jane'])->back()
->complete('kevin --message=')->is(['hello', 'hi', 'greetings'])->back()
->complete('kevin --message=g')->is(['hello', 'hi', 'greetings'])->back()
;
}
}

0 comments on commit d259ce4

Please sign in to comment.