Upgrade 1-11.38

This commit is contained in:
xesmyd
2026-03-30 14:10:30 +02:00
parent f2a7e6d1fc
commit ac648ef29d
24665 changed files with 69682 additions and 2205004 deletions
+1 -1
View File
@@ -68,7 +68,7 @@ class HelperSetTest extends TestCase
} catch (\Exception $e) {
$this->assertInstanceOf('\InvalidArgumentException', $e, '->get() throws InvalidArgumentException when helper not found');
$this->assertInstanceOf('Symfony\Component\Console\Exception\ExceptionInterface', $e, '->get() throws domain specific exception when helper not found');
$this->assertStringContainsString('The helper "foo" is not defined.', $e->getMessage(), '->get() throws InvalidArgumentException when helper not found');
$this->assertContains('The helper "foo" is not defined.', $e->getMessage(), '->get() throws InvalidArgumentException when helper not found');
}
}
+20 -5
View File
@@ -25,6 +25,10 @@ class ProcessHelperTest extends TestCase
*/
public function testVariousProcessRuns($expected, $cmd, $verbosity, $error)
{
if (\is_string($cmd)) {
$cmd = method_exists(Process::class, 'fromShellCommandline') ? Process::fromShellCommandline($cmd) : new Process($cmd);
}
$helper = new ProcessHelper();
$helper->setHelperSet(new HelperSet([new DebugFormatterHelper()]));
$output = $this->getOutputStream($verbosity);
@@ -41,7 +45,7 @@ class ProcessHelperTest extends TestCase
$executed = false;
$callback = function () use (&$executed) { $executed = true; };
$helper->run($output, 'php -r "echo 42;"', null, $callback);
$helper->run($output, ['php', '-r', 'echo 42;'], null, $callback);
$this->assertTrue($executed);
}
@@ -81,12 +85,21 @@ EOT;
OUT out message
RES 252 Command did not run successfully
EOT;
$PHP = '\\' === \DIRECTORY_SEPARATOR ? '"!PHP!"' : '"$PHP"';
$successOutputPhp = <<<EOT
RUN php -r $PHP
OUT 42
RES Command ran successfully
EOT;
$errorMessage = 'An error occurred';
$args = new Process(['php', '-r', 'echo 42;']);
$args = $args->getCommandLine();
$successOutputProcessDebug = str_replace("'php' '-r' 'echo 42;'", $args, $successOutputProcessDebug);
$fromShellCommandline = method_exists(Process::class, 'fromShellCommandline') ? [Process::class, 'fromShellCommandline'] : function ($cmd) { return new Process($cmd); };
return [
['', 'php -r "echo 42;"', StreamOutput::VERBOSITY_VERBOSE, null],
@@ -96,11 +109,13 @@ EOT;
['', 'php -r "syntax error"', StreamOutput::VERBOSITY_VERBOSE, null],
[$syntaxErrorOutputVerbose, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, null],
[$syntaxErrorOutputDebug, 'php -r "fwrite(STDERR, \'error message\');usleep(500000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, null],
[$errorMessage.\PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERBOSE, $errorMessage],
[$syntaxErrorOutputVerbose.$errorMessage.\PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, $errorMessage],
[$syntaxErrorOutputDebug.$errorMessage.\PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(500000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, $errorMessage],
[$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERBOSE, $errorMessage],
[$syntaxErrorOutputVerbose.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(50000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_VERY_VERBOSE, $errorMessage],
[$syntaxErrorOutputDebug.$errorMessage.PHP_EOL, 'php -r "fwrite(STDERR, \'error message\');usleep(500000);fwrite(STDOUT, \'out message\');exit(252);"', StreamOutput::VERBOSITY_DEBUG, $errorMessage],
[$successOutputProcessDebug, ['php', '-r', 'echo 42;'], StreamOutput::VERBOSITY_DEBUG, null],
[$successOutputDebug, new Process('php -r "echo 42;"'), StreamOutput::VERBOSITY_DEBUG, null],
[$successOutputDebug, $fromShellCommandline('php -r "echo 42;"'), StreamOutput::VERBOSITY_DEBUG, null],
[$successOutputProcessDebug, [new Process(['php', '-r', 'echo 42;'])], StreamOutput::VERBOSITY_DEBUG, null],
[$successOutputPhp, [$fromShellCommandline('php -r '.$PHP), 'PHP' => 'echo 42;'], StreamOutput::VERBOSITY_DEBUG, null],
];
}
+120 -13
View File
@@ -12,8 +12,10 @@
namespace Symfony\Component\Console\Tests\Helper;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\StreamOutput;
/**
@@ -323,6 +325,88 @@ class ProgressBarTest extends TestCase
);
}
public function testOverwriteWithSectionOutput()
{
$sections = [];
$stream = $this->getOutputStream(true);
$output = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
$bar = new ProgressBar($output, 50);
$bar->start();
$bar->display();
$bar->advance();
$bar->advance();
rewind($output->getStream());
$this->assertEquals(
' 0/50 [>---------------------------] 0%'.PHP_EOL.
"\x1b[1A\x1b[0J".' 0/50 [>---------------------------] 0%'.PHP_EOL.
"\x1b[1A\x1b[0J".' 1/50 [>---------------------------] 2%'.PHP_EOL.
"\x1b[1A\x1b[0J".' 2/50 [=>--------------------------] 4%'.PHP_EOL,
stream_get_contents($output->getStream())
);
}
public function testOverwriteMultipleProgressBarsWithSectionOutputs()
{
$sections = [];
$stream = $this->getOutputStream(true);
$output1 = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
$output2 = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
$progress = new ProgressBar($output1, 50);
$progress2 = new ProgressBar($output2, 50);
$progress->start();
$progress2->start();
$progress2->advance();
$progress->advance();
rewind($stream->getStream());
$this->assertEquals(
' 0/50 [>---------------------------] 0%'.PHP_EOL.
' 0/50 [>---------------------------] 0%'.PHP_EOL.
"\x1b[1A\x1b[0J".' 1/50 [>---------------------------] 2%'.PHP_EOL.
"\x1b[2A\x1b[0J".' 1/50 [>---------------------------] 2%'.PHP_EOL.
"\x1b[1A\x1b[0J".' 1/50 [>---------------------------] 2%'.PHP_EOL.
' 1/50 [>---------------------------] 2%'.PHP_EOL,
stream_get_contents($stream->getStream())
);
}
public function testMultipleSectionsWithCustomFormat()
{
$sections = [];
$stream = $this->getOutputStream(true);
$output1 = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
$output2 = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
ProgressBar::setFormatDefinition('test', '%current%/%max% [%bar%] %percent:3s%% Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.');
$progress = new ProgressBar($output1, 50);
$progress2 = new ProgressBar($output2, 50);
$progress2->setFormat('test');
$progress->start();
$progress2->start();
$progress->advance();
$progress2->advance();
rewind($stream->getStream());
$this->assertEquals(' 0/50 [>---------------------------] 0%'.PHP_EOL.
' 0/50 [>] 0% Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.'.PHP_EOL.
"\x1b[4A\x1b[0J".' 0/50 [>] 0% Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.'.PHP_EOL.
"\x1b[3A\x1b[0J".' 1/50 [>---------------------------] 2%'.PHP_EOL.
' 0/50 [>] 0% Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.'.PHP_EOL.
"\x1b[3A\x1b[0J".' 1/50 [>] 2% Fruitcake marzipan toffee. Cupcake gummi bears tart dessert ice cream chupa chups cupcake chocolate bar sesame snaps. Croissant halvah cookie jujubes powder macaroon. Fruitcake bear claw bonbon jelly beans oat cake pie muffin Fruitcake marzipan toffee.'.PHP_EOL,
stream_get_contents($stream->getStream())
);
}
public function testStartWithMax()
{
$bar = new ProgressBar($output = $this->getOutputStream());
@@ -477,16 +561,16 @@ class ProgressBarTest extends TestCase
rewind($output->getStream());
$this->assertEquals(
' 0/200 [>---------------------------] 0%'.\PHP_EOL.
' 20/200 [==>-------------------------] 10%'.\PHP_EOL.
' 40/200 [=====>----------------------] 20%'.\PHP_EOL.
' 60/200 [========>-------------------] 30%'.\PHP_EOL.
' 80/200 [===========>----------------] 40%'.\PHP_EOL.
' 100/200 [==============>-------------] 50%'.\PHP_EOL.
' 120/200 [================>-----------] 60%'.\PHP_EOL.
' 140/200 [===================>--------] 70%'.\PHP_EOL.
' 160/200 [======================>-----] 80%'.\PHP_EOL.
' 180/200 [=========================>--] 90%'.\PHP_EOL.
' 0/200 [>---------------------------] 0%'.PHP_EOL.
' 20/200 [==>-------------------------] 10%'.PHP_EOL.
' 40/200 [=====>----------------------] 20%'.PHP_EOL.
' 60/200 [========>-------------------] 30%'.PHP_EOL.
' 80/200 [===========>----------------] 40%'.PHP_EOL.
' 100/200 [==============>-------------] 50%'.PHP_EOL.
' 120/200 [================>-----------] 60%'.PHP_EOL.
' 140/200 [===================>--------] 70%'.PHP_EOL.
' 160/200 [======================>-----] 80%'.PHP_EOL.
' 180/200 [=========================>--] 90%'.PHP_EOL.
' 200/200 [============================] 100%',
stream_get_contents($output->getStream())
);
@@ -503,8 +587,8 @@ class ProgressBarTest extends TestCase
rewind($output->getStream());
$this->assertEquals(
' 0/50 [>---------------------------] 0%'.\PHP_EOL.
' 25/50 [==============>-------------] 50%'.\PHP_EOL.
' 0/50 [>---------------------------] 0%'.PHP_EOL.
' 25/50 [==============>-------------] 50%'.PHP_EOL.
' 50/50 [============================] 100%',
stream_get_contents($output->getStream())
);
@@ -518,7 +602,7 @@ class ProgressBarTest extends TestCase
rewind($output->getStream());
$this->assertEquals(
' 0 [>---------------------------]'.\PHP_EOL.
' 0 [>---------------------------]'.PHP_EOL.
' 1 [->--------------------------]',
stream_get_contents($output->getStream())
);
@@ -605,6 +689,29 @@ class ProgressBarTest extends TestCase
);
}
public function testSettingMaxStepsDuringProgressing()
{
$output = $this->getOutputStream();
$bar = new ProgressBar($output);
$bar->start();
$bar->setProgress(2);
$bar->setMaxSteps(10);
$bar->setProgress(5);
$bar->setMaxSteps(100);
$bar->setProgress(10);
$bar->finish();
rewind($output->getStream());
$this->assertEquals(
rtrim(' 0 [>---------------------------]').
rtrim($this->generateOutput(' 2 [-->-------------------------]')).
rtrim($this->generateOutput(' 5/10 [==============>-------------] 50%')).
rtrim($this->generateOutput(' 10/100 [==>-------------------------] 10%')).
rtrim($this->generateOutput(' 100/100 [============================] 100%')),
stream_get_contents($output->getStream())
);
}
public function testWithSmallScreen()
{
$output = $this->getOutputStream();
+22 -14
View File
@@ -46,11 +46,11 @@ class ProgressIndicatorTest extends TestCase
$this->generateOutput(' \\ Advancing...').
$this->generateOutput(' | Advancing...').
$this->generateOutput(' | Done...').
\PHP_EOL.
PHP_EOL.
$this->generateOutput(' - Starting Again...').
$this->generateOutput(' \\ Starting Again...').
$this->generateOutput(' \\ Done Again...').
\PHP_EOL,
PHP_EOL,
stream_get_contents($output->getStream())
);
}
@@ -70,9 +70,9 @@ class ProgressIndicatorTest extends TestCase
rewind($output->getStream());
$this->assertEquals(
' Starting...'.\PHP_EOL.
' Midway...'.\PHP_EOL.
' Done...'.\PHP_EOL.\PHP_EOL,
' Starting...'.PHP_EOL.
' Midway...'.PHP_EOL.
' Done...'.PHP_EOL.PHP_EOL,
stream_get_contents($output->getStream())
);
}
@@ -100,34 +100,42 @@ class ProgressIndicatorTest extends TestCase
);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Must have at least 2 indicator value characters.
*/
public function testCannotSetInvalidIndicatorCharacters()
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Must have at least 2 indicator value characters.');
new ProgressIndicator($this->getOutputStream(), null, 100, ['1']);
$bar = new ProgressIndicator($this->getOutputStream(), null, 100, ['1']);
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Progress indicator already started.
*/
public function testCannotStartAlreadyStartedIndicator()
{
$this->expectException('LogicException');
$this->expectExceptionMessage('Progress indicator already started.');
$bar = new ProgressIndicator($this->getOutputStream());
$bar->start('Starting...');
$bar->start('Starting Again.');
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Progress indicator has not yet been started.
*/
public function testCannotAdvanceUnstartedIndicator()
{
$this->expectException('LogicException');
$this->expectExceptionMessage('Progress indicator has not yet been started.');
$bar = new ProgressIndicator($this->getOutputStream());
$bar->advance();
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Progress indicator has not yet been started.
*/
public function testCannotFinishUnstartedIndicator()
{
$this->expectException('LogicException');
$this->expectExceptionMessage('Progress indicator has not yet been started.');
$bar = new ProgressIndicator($this->getOutputStream());
$bar->finish('Finished');
}
+37 -406
View File
@@ -11,7 +11,6 @@
namespace Symfony\Component\Console\Tests\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\HelperSet;
@@ -20,7 +19,6 @@ use Symfony\Component\Console\Output\StreamOutput;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;
/**
* @group tty
@@ -55,7 +53,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
rewind($output->getStream());
$stream = stream_get_contents($output->getStream());
$this->assertStringContainsString('Input "Fabien" is not a superhero!', $stream);
$this->assertContains('Input "Fabien" is not a superhero!', $stream);
try {
$question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '1');
@@ -169,27 +167,26 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
public function testAskWithAutocomplete()
{
if (!Terminal::hasSttyAvailable()) {
if (!$this->hasSttyAvailable()) {
$this->markTestSkipped('`stty` is required to test autocomplete functionality');
}
// Acm<NEWLINE>
// Ac<BACKSPACE><BACKSPACE>s<TAB>Test<NEWLINE>
// <NEWLINE>
// <UP ARROW><UP ARROW><UP ARROW><NEWLINE>
// <UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><TAB>Test<NEWLINE>
// <UP ARROW><UP ARROW><NEWLINE>
// <UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><TAB>Test<NEWLINE>
// <DOWN ARROW><NEWLINE>
// S<BACKSPACE><BACKSPACE><DOWN ARROW><DOWN ARROW><NEWLINE>
// F00<BACKSPACE><BACKSPACE>oo<TAB><NEWLINE>
// F⭐<TAB><BACKSPACE><BACKSPACE>⭐<TAB><NEWLINE>
$inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\nF⭐\t\177\177\t\n");
$inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\n");
$dialog = new QuestionHelper();
$helperSet = new HelperSet([new FormatterHelper()]);
$dialog->setHelperSet($helperSet);
$question = new Question('Please select a bundle', 'FrameworkBundle');
$question->setAutocompleterValues(['AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle', 'F⭐Y']);
$question->setAutocompleterValues(['AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle']);
$this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
$this->assertEquals('AsseticBundleTest', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
@@ -199,12 +196,11 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
$this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
$this->assertEquals('AsseticBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
$this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
$this->assertEquals('F⭐Y', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
}
public function testAskWithAutocompleteWithNonSequentialKeys()
{
if (!Terminal::hasSttyAvailable()) {
if (!$this->hasSttyAvailable()) {
$this->markTestSkipped('`stty` is required to test autocomplete functionality');
}
@@ -223,7 +219,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
public function testAskWithAutocompleteWithExactMatch()
{
if (!Terminal::hasSttyAvailable()) {
if (!$this->hasSttyAvailable()) {
$this->markTestSkipped('`stty` is required to test autocomplete functionality');
}
@@ -259,7 +255,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
*/
public function testAskWithAutocompleteWithMultiByteCharacter($character)
{
if (!Terminal::hasSttyAvailable()) {
if (!$this->hasSttyAvailable()) {
$this->markTestSkipped('`stty` is required to test autocomplete functionality');
}
@@ -283,7 +279,7 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
public function testAutocompleteWithTrailingBackslash()
{
if (!Terminal::hasSttyAvailable()) {
if (!$this->hasSttyAvailable()) {
$this->markTestSkipped('`stty` is required to test autocomplete functionality');
}
@@ -522,10 +518,12 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
$this->assertSame($expectedValue, $answer);
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage The provided answer is ambiguous. Value should be one of env_2 or env_3.
*/
public function testAmbiguousChoiceFromChoicelist()
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('The provided answer is ambiguous. Value should be one of "env_2" or "env_3".');
$possibleChoices = [
'env_1' => 'My first environment',
'env_2' => 'My environment',
@@ -590,382 +588,35 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
}
/**
* @group legacy
* @expectedException \Symfony\Component\Console\Exception\RuntimeException
* @expectedExceptionMessage Aborted.
*/
public function testLegacyAskChoice()
{
$questionHelper = new QuestionHelper();
$helperSet = new HelperSet([new FormatterHelper()]);
$questionHelper->setHelperSet($helperSet);
$heroes = ['Superman', 'Batman', 'Spiderman'];
$questionHelper->setInputStream($this->getInputStream("\n1\n 1 \nFabien\n1\nFabien\n1\n0,2\n 0 , 2 \n\n\n"));
$question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '2');
$question->setMaxAttempts(1);
// first answer is an empty answer, we're supposed to receive the default value
$this->assertEquals('Spiderman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$question = new ChoiceQuestion('What is your favorite superhero?', $heroes);
$question->setMaxAttempts(1);
$this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$question = new ChoiceQuestion('What is your favorite superhero?', $heroes);
$question->setErrorMessage('Input "%s" is not a superhero!');
$question->setMaxAttempts(2);
$this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question));
rewind($output->getStream());
$stream = stream_get_contents($output->getStream());
$this->assertStringContainsString('Input "Fabien" is not a superhero!', $stream);
try {
$question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '1');
$question->setMaxAttempts(1);
$questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question);
$this->fail();
} catch (\InvalidArgumentException $e) {
$this->assertEquals('Value "Fabien" is invalid', $e->getMessage());
}
$question = new ChoiceQuestion('What is your favorite superhero?', $heroes, null);
$question->setMaxAttempts(1);
$question->setMultiselect(true);
$this->assertEquals(['Batman'], $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$this->assertEquals(['Superman', 'Spiderman'], $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$this->assertEquals(['Superman', 'Spiderman'], $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '0,1');
$question->setMaxAttempts(1);
$question->setMultiselect(true);
$this->assertEquals(['Superman', 'Batman'], $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$question = new ChoiceQuestion('What is your favorite superhero?', $heroes, ' 0 , 1 ');
$question->setMaxAttempts(1);
$question->setMultiselect(true);
$this->assertEquals(['Superman', 'Batman'], $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
}
/**
* @group legacy
*/
public function testLegacyAsk()
{
$dialog = new QuestionHelper();
$dialog->setInputStream($this->getInputStream("\n8AM\n"));
$question = new Question('What time is it?', '2PM');
$this->assertEquals('2PM', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$question = new Question('What time is it?', '2PM');
$this->assertEquals('8AM', $dialog->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question));
rewind($output->getStream());
$this->assertEquals('What time is it?', stream_get_contents($output->getStream()));
}
/**
* @group legacy
*/
public function testLegacyAskWithAutocomplete()
{
if (!Terminal::hasSttyAvailable()) {
$this->markTestSkipped('`stty` is required to test autocomplete functionality');
}
// Acm<NEWLINE>
// Ac<BACKSPACE><BACKSPACE>s<TAB>Test<NEWLINE>
// <NEWLINE>
// <UP ARROW><UP ARROW><UP ARROW><NEWLINE>
// <UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><TAB>Test<NEWLINE>
// <DOWN ARROW><NEWLINE>
// S<BACKSPACE><BACKSPACE><DOWN ARROW><DOWN ARROW><NEWLINE>
// F00<BACKSPACE><BACKSPACE>oo<TAB><NEWLINE>
// F⭐<TAB><BACKSPACE><BACKSPACE>⭐<TAB><NEWLINE>
$inputStream = $this->getInputStream("Acm\nAc\177\177s\tTest\n\n\033[A\033[A\033[A\n\033[A\033[A\033[A\033[A\033[A\033[A\033[A\tTest\n\033[B\nS\177\177\033[B\033[B\nF00\177\177oo\t\nF⭐\t\t\n");
$dialog = new QuestionHelper();
$dialog->setInputStream($inputStream);
$helperSet = new HelperSet([new FormatterHelper()]);
$dialog->setHelperSet($helperSet);
$question = new Question('Please select a bundle', 'FrameworkBundle');
$question->setAutocompleterValues(['AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle', 'F⭐Y']);
$this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$this->assertEquals('AsseticBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$this->assertEquals('FrameworkBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$this->assertEquals('SecurityBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$this->assertEquals('FooBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$this->assertEquals('AsseticBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$this->assertEquals('FooBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$this->assertEquals('F⭐Y', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
}
/**
* @group legacy
*/
public function testLegacyAskWithAutocompleteWithNonSequentialKeys()
{
if (!Terminal::hasSttyAvailable()) {
$this->markTestSkipped('`stty` is required to test autocomplete functionality');
}
// <UP ARROW><UP ARROW><NEWLINE><DOWN ARROW><DOWN ARROW><NEWLINE>
$inputStream = $this->getInputStream("\033[A\033[A\n\033[B\033[B\n");
$dialog = new QuestionHelper();
$dialog->setInputStream($inputStream);
$dialog->setHelperSet(new HelperSet([new FormatterHelper()]));
$question = new ChoiceQuestion('Please select a bundle', [1 => 'AcmeDemoBundle', 4 => 'AsseticBundle']);
$question->setMaxAttempts(1);
$this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$this->assertEquals('AsseticBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
}
/**
* @group legacy
*/
public function testLegacyAskHiddenResponse()
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$this->markTestSkipped('This test is not supported on Windows');
}
$dialog = new QuestionHelper();
$dialog->setInputStream($this->getInputStream("8AM\n"));
$question = new Question('What time is it?');
$question->setHidden(true);
$this->assertEquals('8AM', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
}
/**
* @group legacy
* @dataProvider getAskConfirmationData
*/
public function testLegacyAskConfirmation($question, $expected, $default = true)
{
$dialog = new QuestionHelper();
$dialog->setInputStream($this->getInputStream($question."\n"));
$question = new ConfirmationQuestion('Do you like French fries?', $default);
$this->assertEquals($expected, $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question), 'confirmation question should '.($expected ? 'pass' : 'cancel'));
}
/**
* @group legacy
*/
public function testLegacyAskConfirmationWithCustomTrueAnswer()
{
$dialog = new QuestionHelper();
$dialog->setInputStream($this->getInputStream("j\ny\n"));
$question = new ConfirmationQuestion('Do you like French fries?', false, '/^(j|y)/i');
$this->assertTrue($dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$question = new ConfirmationQuestion('Do you like French fries?', false, '/^(j|y)/i');
$this->assertTrue($dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
}
/**
* @group legacy
*/
public function testLegacyAskAndValidate()
{
$dialog = new QuestionHelper();
$helperSet = new HelperSet([new FormatterHelper()]);
$dialog->setHelperSet($helperSet);
$error = 'This is not a color!';
$validator = function ($color) use ($error) {
if (!\in_array($color, ['white', 'black'])) {
throw new \InvalidArgumentException($error);
}
return $color;
};
$question = new Question('What color was the white horse of Henry IV?', 'white');
$question->setValidator($validator);
$question->setMaxAttempts(2);
$dialog->setInputStream($this->getInputStream("\nblack\n"));
$this->assertEquals('white', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$this->assertEquals('black', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
$dialog->setInputStream($this->getInputStream("green\nyellow\norange\n"));
try {
$dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question);
$this->fail();
} catch (\InvalidArgumentException $e) {
$this->assertEquals($error, $e->getMessage());
}
}
/**
* @group legacy
* @dataProvider simpleAnswerProvider
*/
public function testLegacySelectChoiceFromSimpleChoices($providedAnswer, $expectedValue)
{
$possibleChoices = [
'My environment 1',
'My environment 2',
'My environment 3',
];
$dialog = new QuestionHelper();
$dialog->setInputStream($this->getInputStream($providedAnswer."\n"));
$helperSet = new HelperSet([new FormatterHelper()]);
$dialog->setHelperSet($helperSet);
$question = new ChoiceQuestion('Please select the environment to load', $possibleChoices);
$question->setMaxAttempts(1);
$answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question);
$this->assertSame($expectedValue, $answer);
}
/**
* @group legacy
* @dataProvider mixedKeysChoiceListAnswerProvider
*/
public function testLegacyChoiceFromChoicelistWithMixedKeys($providedAnswer, $expectedValue)
{
$possibleChoices = [
'0' => 'No environment',
'1' => 'My environment 1',
'env_2' => 'My environment 2',
3 => 'My environment 3',
];
$dialog = new QuestionHelper();
$dialog->setInputStream($this->getInputStream($providedAnswer."\n"));
$helperSet = new HelperSet([new FormatterHelper()]);
$dialog->setHelperSet($helperSet);
$question = new ChoiceQuestion('Please select the environment to load', $possibleChoices);
$question->setMaxAttempts(1);
$answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question);
$this->assertSame($expectedValue, $answer);
}
/**
* @group legacy
* @dataProvider answerProvider
*/
public function testLegacySelectChoiceFromChoiceList($providedAnswer, $expectedValue)
{
$possibleChoices = [
'env_1' => 'My environment 1',
'env_2' => 'My environment',
'env_3' => 'My environment',
];
$dialog = new QuestionHelper();
$dialog->setInputStream($this->getInputStream($providedAnswer."\n"));
$helperSet = new HelperSet([new FormatterHelper()]);
$dialog->setHelperSet($helperSet);
$question = new ChoiceQuestion('Please select the environment to load', $possibleChoices);
$question->setMaxAttempts(1);
$answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question);
$this->assertSame($expectedValue, $answer);
}
/**
* @group legacy
*/
public function testLegacyAmbiguousChoiceFromChoicelist()
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('The provided answer is ambiguous. Value should be one of "env_2" or "env_3".');
$possibleChoices = [
'env_1' => 'My first environment',
'env_2' => 'My environment',
'env_3' => 'My environment',
];
$dialog = new QuestionHelper();
$dialog->setInputStream($this->getInputStream("My environment\n"));
$helperSet = new HelperSet([new FormatterHelper()]);
$dialog->setHelperSet($helperSet);
$question = new ChoiceQuestion('Please select the environment to load', $possibleChoices);
$question->setMaxAttempts(1);
$dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question);
}
/**
* @requires function mb_strwidth
* @group legacy
*/
public function testLegacyChoiceOutputFormattingQuestionForUtf8Keys()
{
$question = 'Lorem ipsum?';
$possibleChoices = [
'foo' => 'foo',
'żółw' => 'bar',
'łabądź' => 'baz',
];
$outputShown = [
$question,
' [<info>foo </info>] foo',
' [<info>żółw </info>] bar',
' [<info>łabądź</info>] baz',
];
$output = $this->getMockBuilder('\Symfony\Component\Console\Output\OutputInterface')->getMock();
$output->method('getFormatter')->willReturn(new OutputFormatter());
$dialog = new QuestionHelper();
$dialog->setInputStream($this->getInputStream("\n"));
$helperSet = new HelperSet([new FormatterHelper()]);
$dialog->setHelperSet($helperSet);
$output->expects($this->once())->method('writeln')->with($this->equalTo($outputShown));
$question = new ChoiceQuestion($question, $possibleChoices, 'foo');
$dialog->ask($this->createInputInterfaceMock(), $output, $question);
}
public function testAskThrowsExceptionOnMissingInput()
{
$this->expectException('Symfony\Component\Console\Exception\RuntimeException');
$this->expectExceptionMessage('Aborted.');
$dialog = new QuestionHelper();
$dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), new Question('What\'s your name?'));
}
/**
* @expectedException \Symfony\Component\Console\Exception\RuntimeException
* @expectedExceptionMessage Aborted.
*/
public function testAskThrowsExceptionOnMissingInputForChoiceQuestion()
{
$this->expectException('Symfony\Component\Console\Exception\RuntimeException');
$this->expectExceptionMessage('Aborted.');
$dialog = new QuestionHelper();
$dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), new ChoiceQuestion('Choice', ['a', 'b']));
}
/**
* @expectedException \Symfony\Component\Console\Exception\RuntimeException
* @expectedExceptionMessage Aborted.
*/
public function testAskThrowsExceptionOnMissingInputWithValidator()
{
$this->expectException('Symfony\Component\Console\Exception\RuntimeException');
$this->expectExceptionMessage('Aborted.');
$dialog = new QuestionHelper();
$question = new Question('What\'s your name?');
$question->setValidator(function ($value) {
$question->setValidator(function () {
if (!$value) {
throw new \Exception('A value is required.');
}
@@ -974,16 +625,18 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
$dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), $question);
}
/**
* @expectedException \LogicException
* @expectedExceptionMessage Choice question must have at least 1 choice available.
*/
public function testEmptyChoices()
{
$this->expectException('LogicException');
$this->expectExceptionMessage('Choice question must have at least 1 choice available.');
new ChoiceQuestion('Question', [], 'irrelevant');
}
public function testTraversableAutocomplete()
{
if (!Terminal::hasSttyAvailable()) {
if (!$this->hasSttyAvailable()) {
$this->markTestSkipped('`stty` is required to test autocomplete functionality');
}
@@ -1014,35 +667,6 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
$this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
}
public function testDisableStty()
{
if (!Terminal::hasSttyAvailable()) {
$this->markTestSkipped('`stty` is required to test autocomplete functionality');
}
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('invalid');
QuestionHelper::disableStty();
$dialog = new QuestionHelper();
$dialog->setHelperSet(new HelperSet([new FormatterHelper()]));
$question = new ChoiceQuestion('Please select a bundle', [1 => 'AcmeDemoBundle', 4 => 'AsseticBundle']);
$question->setMaxAttempts(1);
// <UP ARROW><UP ARROW><NEWLINE><DOWN ARROW><DOWN ARROW><NEWLINE>
// Gives `AcmeDemoBundle` with stty
$inputStream = $this->getInputStream("\033[A\033[A\n\033[B\033[B\n");
try {
$dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question);
} finally {
$reflection = new \ReflectionProperty(QuestionHelper::class, 'stty');
$reflection->setAccessible(true);
$reflection->setValue(null, true);
}
}
public function testTraversableMultiselectAutocomplete()
{
// <NEWLINE>
@@ -1097,6 +721,13 @@ class QuestionHelperTest extends AbstractQuestionHelperTest
return $mock;
}
private function hasSttyAvailable()
{
exec('stty 2>&1', $output, $exitcode);
return 0 === $exitcode;
}
}
class AutocompleteValues implements \IteratorAggregate
@@ -122,57 +122,16 @@ class SymfonyQuestionHelperTest extends AbstractQuestionHelperTest
$this->assertOutputContains('Question with a trailing \\', $output);
}
/**
* @expectedException \Symfony\Component\Console\Exception\RuntimeException
* @expectedExceptionMessage Aborted.
*/
public function testAskThrowsExceptionOnMissingInput()
{
$this->expectException('Symfony\Component\Console\Exception\RuntimeException');
$this->expectExceptionMessage('Aborted.');
$dialog = new SymfonyQuestionHelper();
$dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), new Question('What\'s your name?'));
}
public function testChoiceQuestionPadding()
{
$choiceQuestion = new ChoiceQuestion('qqq', [
'foo' => 'foo',
'żółw' => 'bar',
'łabądź' => 'baz',
]);
(new SymfonyQuestionHelper())->ask(
$this->createStreamableInputInterfaceMock($this->getInputStream("foo\n")),
$output = $this->createOutputInterface(),
$choiceQuestion
);
$this->assertOutputContains(<<<EOT
qqq:
[foo ] foo
[żółw ] bar
[łabądź] baz
>
EOT
, $output, true);
}
public function testChoiceQuestionCustomPrompt()
{
$choiceQuestion = new ChoiceQuestion('qqq', ['foo']);
$choiceQuestion->setPrompt(' >ccc> ');
(new SymfonyQuestionHelper())->ask(
$this->createStreamableInputInterfaceMock($this->getInputStream("foo\n")),
$output = $this->createOutputInterface(),
$choiceQuestion
);
$this->assertOutputContains(<<<EOT
qqq:
[0] foo
>ccc>
EOT
, $output, true);
}
protected function getInputStream($input)
{
$stream = fopen('php://memory', 'r+', false);
@@ -200,15 +159,10 @@ EOT
return $mock;
}
private function assertOutputContains($expected, StreamOutput $output, $normalize = false)
private function assertOutputContains($expected, StreamOutput $output)
{
rewind($output->getStream());
$stream = stream_get_contents($output->getStream());
if ($normalize) {
$stream = str_replace(\PHP_EOL, "\n", $stream);
}
$this->assertStringContainsString($expected, $stream);
$this->assertContains($expected, $stream);
}
}
+4 -2
View File
@@ -16,10 +16,12 @@ use Symfony\Component\Console\Helper\TableStyle;
class TableStyleTest extends TestCase
{
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).
*/
public function testSetPadTypeWithInvalidType()
{
$this->expectException('InvalidArgumentException');
$this->expectExceptionMessage('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).');
$style = new TableStyle();
$style->setPadType('TEST');
}
+351 -20
View File
@@ -12,10 +12,12 @@
namespace Symfony\Component\Console\Tests\Helper;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Helper\TableStyle;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\StreamOutput;
class TableTest extends TestCase
@@ -136,6 +138,45 @@ TABLE
80-902734-1-6 And Then There Were None Agatha Christie
=============== ========================== ==================
TABLE
],
[
['ISBN', 'Title', 'Author'],
$books,
'box',
<<<'TABLE'
┌───────────────┬──────────────────────────┬──────────────────┐
│ ISBN │ Title │ Author │
├───────────────┼──────────────────────────┼──────────────────┤
│ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri │
│ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens │
│ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien │
│ 80-902734-1-6 │ And Then There Were None │ Agatha Christie │
└───────────────┴──────────────────────────┴──────────────────┘
TABLE
],
[
['ISBN', 'Title', 'Author'],
[
['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'],
['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'],
new TableSeparator(),
['960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'],
['80-902734-1-6', 'And Then There Were None', 'Agatha Christie'],
],
'box-double',
<<<'TABLE'
╔═══════════════╤══════════════════════════╤══════════════════╗
║ ISBN │ Title │ Author ║
╠═══════════════╪══════════════════════════╪══════════════════╣
║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
╟───────────────┼──────────────────────────┼──────────────────╢
║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
╚═══════════════╧══════════════════════════╧══════════════════╝
TABLE
],
[
@@ -610,9 +651,9 @@ TABLE;
{
$style = new TableStyle();
$style
->setHorizontalBorderChar('.')
->setVerticalBorderChar('.')
->setCrossingChar('.')
->setHorizontalBorderChars('.')
->setVerticalBorderChars('.')
->setDefaultCrossingChar('.')
;
Table::setStyleDefinition('dotfull', $style);
@@ -707,7 +748,7 @@ TABLE;
]);
$style = new TableStyle();
$style->setPadType(\STR_PAD_LEFT);
$style->setPadType(STR_PAD_LEFT);
$table->setColumnStyle(3, $style);
$table->render();
@@ -726,10 +767,12 @@ TABLE;
$this->assertEquals($expected, $this->getOutputContent($output));
}
/**
* @expectedException \Symfony\Component\Console\Exception\InvalidArgumentException
* @expectedExceptionMessage A cell must be a TableCell, a scalar or an object implementing __toString, array given.
*/
public function testThrowsWhenTheCellInAnArray()
{
$this->expectException('Symfony\Component\Console\Exception\InvalidArgumentException');
$this->expectExceptionMessage('A cell must be a TableCell, a scalar or an object implementing "__toString()", "array" given.');
$table = new Table($output = $this->getOutputStream());
$table
->setHeaders(['ISBN', 'Title', 'Author', 'Price'])
@@ -753,7 +796,7 @@ TABLE;
->setColumnWidth(3, 10);
$style = new TableStyle();
$style->setPadType(\STR_PAD_LEFT);
$style->setPadType(STR_PAD_LEFT);
$table->setColumnStyle(3, $style);
$table->render();
@@ -784,7 +827,7 @@ TABLE;
->setColumnWidths([15, 0, -1, 10]);
$style = new TableStyle();
$style->setPadType(\STR_PAD_LEFT);
$style->setPadType(STR_PAD_LEFT);
$table->setColumnStyle(3, $style);
$table->render();
@@ -803,28 +846,264 @@ TABLE;
$this->assertEquals($expected, $this->getOutputContent($output));
}
public function testSectionOutput()
{
$sections = [];
$stream = $this->getOutputStream(true);
$output = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
$table = new Table($output);
$table
->setHeaders(['ISBN', 'Title', 'Author', 'Price'])
->setRows([
['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'],
]);
$table->render();
$table->appendRow(['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25']);
$expected =
<<<TABLE
+---------------+---------------+-----------------+-------+
|\033[32m ISBN \033[39m|\033[32m Title \033[39m|\033[32m Author \033[39m|\033[32m Price \033[39m|
+---------------+---------------+-----------------+-------+
| 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
+---------------+---------------+-----------------+-------+
\x1b[5A\x1b[0J+---------------+----------------------+-----------------+--------+
|\033[32m ISBN \033[39m|\033[32m Title \033[39m|\033[32m Author \033[39m|\033[32m Price \033[39m|
+---------------+----------------------+-----------------+--------+
| 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 139.25 |
+---------------+----------------------+-----------------+--------+
TABLE;
$this->assertEquals($expected, $this->getOutputContent($output));
}
public function testSectionOutputDoesntClearIfTableIsntRendered()
{
$sections = [];
$stream = $this->getOutputStream(true);
$output = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
$table = new Table($output);
$table
->setHeaders(['ISBN', 'Title', 'Author', 'Price'])
->setRows([
['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'],
]);
$table->appendRow(['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25']);
$expected =
<<<TABLE
+---------------+----------------------+-----------------+--------+
|\033[32m ISBN \033[39m|\033[32m Title \033[39m|\033[32m Author \033[39m|\033[32m Price \033[39m|
+---------------+----------------------+-----------------+--------+
| 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 139.25 |
+---------------+----------------------+-----------------+--------+
TABLE;
$this->assertEquals($expected, $this->getOutputContent($output));
}
public function testSectionOutputWithoutDecoration()
{
$sections = [];
$stream = $this->getOutputStream();
$output = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
$table = new Table($output);
$table
->setHeaders(['ISBN', 'Title', 'Author', 'Price'])
->setRows([
['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'],
]);
$table->render();
$table->appendRow(['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25']);
$expected =
<<<TABLE
+---------------+---------------+-----------------+-------+
| ISBN | Title | Author | Price |
+---------------+---------------+-----------------+-------+
| 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
+---------------+---------------+-----------------+-------+
+---------------+----------------------+-----------------+--------+
| ISBN | Title | Author | Price |
+---------------+----------------------+-----------------+--------+
| 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 139.25 |
+---------------+----------------------+-----------------+--------+
TABLE;
$this->assertEquals($expected, $this->getOutputContent($output));
}
/**
* @expectedException \Symfony\Component\Console\Exception\RuntimeException
* @expectedExceptionMessage Output should be an instance of "Symfony\Component\Console\Output\ConsoleSectionOutput" when calling "Symfony\Component\Console\Helper\Table::appendRow".
*/
public function testAppendRowWithoutSectionOutput()
{
$table = new Table($this->getOutputStream());
$table->appendRow(['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25']);
}
/**
* @expectedException \Symfony\Component\Console\Exception\InvalidArgumentException
* @expectedExceptionMessage Style "absent" is not defined.
*/
public function testIsNotDefinedStyleException()
{
$this->expectException('Symfony\Component\Console\Exception\InvalidArgumentException');
$this->expectExceptionMessage('Style "absent" is not defined.');
$table = new Table($this->getOutputStream());
$table->setStyle('absent');
}
/**
* @expectedException \Symfony\Component\Console\Exception\InvalidArgumentException
* @expectedExceptionMessage Style "absent" is not defined.
*/
public function testGetStyleDefinition()
{
$this->expectException('Symfony\Component\Console\Exception\InvalidArgumentException');
$this->expectExceptionMessage('Style "absent" is not defined.');
Table::getStyleDefinition('absent');
}
/**
* @dataProvider renderSetTitle
*/
public function testSetTitle($headerTitle, $footerTitle, $style, $expected)
{
(new Table($output = $this->getOutputStream()))
->setHeaderTitle($headerTitle)
->setFooterTitle($footerTitle)
->setHeaders(['ISBN', 'Title', 'Author'])
->setRows([
['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'],
['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'],
['960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'],
['80-902734-1-6', 'And Then There Were None', 'Agatha Christie'],
])
->setStyle($style)
->render()
;
$this->assertEquals($expected, $this->getOutputContent($output));
}
public function renderSetTitle()
{
return [
[
'Books',
'Page 1/2',
'default',
<<<'TABLE'
+---------------+----------- Books --------+------------------+
| ISBN | Title | Author |
+---------------+--------------------------+------------------+
| 99921-58-10-7 | Divine Comedy | Dante Alighieri |
| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
| 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
| 80-902734-1-6 | And Then There Were None | Agatha Christie |
+---------------+--------- Page 1/2 -------+------------------+
TABLE
],
[
'Books',
'Page 1/2',
'box',
<<<'TABLE'
┌───────────────┬─────────── Books ────────┬──────────────────┐
│ ISBN │ Title │ Author │
├───────────────┼──────────────────────────┼──────────────────┤
│ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri │
│ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens │
│ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien │
│ 80-902734-1-6 │ And Then There Were None │ Agatha Christie │
└───────────────┴───────── Page 1/2 ───────┴──────────────────┘
TABLE
],
[
'Boooooooooooooooooooooooooooooooooooooooooooooooooooooooks',
'Page 1/999999999999999999999999999999999999999999999999999',
'default',
<<<'TABLE'
+- Booooooooooooooooooooooooooooooooooooooooooooooooooooo... -+
| ISBN | Title | Author |
+---------------+--------------------------+------------------+
| 99921-58-10-7 | Divine Comedy | Dante Alighieri |
| 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
| 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
| 80-902734-1-6 | And Then There Were None | Agatha Christie |
+- Page 1/99999999999999999999999999999999999999999999999... -+
TABLE
],
];
}
public function testColumnMaxWidths()
{
$table = new Table($output = $this->getOutputStream());
$table
->setRows([
['Divine Comedy', 'A Tale of Two Cities', 'The Lord of the Rings', 'And Then There Were None'],
])
->setColumnMaxWidth(1, 5)
->setColumnMaxWidth(2, 10)
->setColumnMaxWidth(3, 15);
$table->render();
$expected =
<<<TABLE
+---------------+-------+------------+-----------------+
| Divine Comedy | A Tal | The Lord o | And Then There |
| | e of | f the Ring | Were None |
| | Two C | s | |
| | ities | | |
+---------------+-------+------------+-----------------+
TABLE;
$this->assertEquals($expected, $this->getOutputContent($output));
}
public function testColumnMaxWidthsWithTrailingBackslash()
{
(new Table($output = $this->getOutputStream()))
->setColumnMaxWidth(0, 5)
->setRows([['1234\6']])
->render()
;
$expected =
<<<'TABLE'
+-------+
| 1234\ |
| 6 |
+-------+
TABLE;
$this->assertEquals($expected, $this->getOutputContent($output));
}
public function testBoxedStyleWithColspan()
{
$boxed = new TableStyle();
$boxed
->setHorizontalBorderChar('─')
->setVerticalBorderChar('│')
->setCrossingChar('┼')
->setHorizontalBorderChars('─')
->setVerticalBorderChars('│')
->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├')
;
$table = new Table($output = $this->getOutputStream());
@@ -841,13 +1120,13 @@ TABLE;
$expected =
<<<TABLE
───────────────────────────────────────────────
───────────────────────────────────────────────
│ ISBN │ Title │ Author │
───────────────┼───────────────┼─────────────────
───────────────┼───────────────┼─────────────────
│ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri │
───────────────┼───────────────┼─────────────────
───────────────┼───────────────┼─────────────────
│ This value spans 3 columns. │
───────────────────────────────────────────────
───────────────────────────────────────────────
TABLE;
@@ -863,6 +1142,58 @@ TABLE;
{
rewind($output->getStream());
return str_replace(\PHP_EOL, "\n", stream_get_contents($output->getStream()));
return str_replace(PHP_EOL, "\n", stream_get_contents($output->getStream()));
}
public function testWithColspanAndMaxWith(): void
{
$table = new Table($output = $this->getOutputStream());
$table->setColumnMaxWidth(0, 15);
$table->setColumnMaxWidth(1, 15);
$table->setColumnMaxWidth(2, 15);
$table->setRows([
[new TableCell('Lorem ipsum dolor sit amet, <fg=white;bg=green>consectetur</> adipiscing elit, <fg=white;bg=red>sed</> do <fg=white;bg=red>eiusmod</> tempor', ['colspan' => 3])],
new TableSeparator(),
[new TableCell('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor', ['colspan' => 3])],
new TableSeparator(),
[new TableCell('Lorem ipsum <fg=white;bg=red>dolor</> sit amet, consectetur ', ['colspan' => 2]), 'hello world'],
new TableSeparator(),
['hello <fg=white;bg=green>world</>', new TableCell('Lorem ipsum dolor sit amet, <fg=white;bg=green>consectetur</> adipiscing elit', ['colspan' => 2])],
new TableSeparator(),
['hello ', new TableCell('world', ['colspan' => 1]), 'Lorem ipsum dolor sit amet, consectetur'],
new TableSeparator(),
['Symfony ', new TableCell('Test', ['colspan' => 1]), 'Lorem <fg=white;bg=green>ipsum</> dolor sit amet, consectetur'],
])
;
$table->render();
$expected =
<<<TABLE
+-----------------+-----------------+-----------------+
| Lorem ipsum dolor sit amet, consectetur adipi |
| scing elit, sed do eiusmod tempor |
+-----------------+-----------------+-----------------+
| Lorem ipsum dolor sit amet, consectetur adipi |
| scing elit, sed do eiusmod tempor |
+-----------------+-----------------+-----------------+
| Lorem ipsum dolor sit amet, co | hello world |
| nsectetur | |
+-----------------+-----------------+-----------------+
| hello world | Lorem ipsum dolor sit amet, co |
| | nsectetur adipiscing elit |
+-----------------+-----------------+-----------------+
| hello | world | Lorem ipsum dol |
| | | or sit amet, co |
| | | nsectetur |
+-----------------+-----------------+-----------------+
| Symfony | Test | Lorem ipsum dol |
| | | or sit amet, co |
| | | nsectetur |
+-----------------+-----------------+-----------------+
TABLE;
$this->assertSame($expected, $this->getOutputContent($output));
}
}