1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console\Tests\Helper;
13
14use Symfony\Component\Console\Exception\InvalidArgumentException;
15use Symfony\Component\Console\Formatter\OutputFormatter;
16use Symfony\Component\Console\Helper\FormatterHelper;
17use Symfony\Component\Console\Helper\HelperSet;
18use Symfony\Component\Console\Helper\QuestionHelper;
19use Symfony\Component\Console\Output\StreamOutput;
20use Symfony\Component\Console\Question\ChoiceQuestion;
21use Symfony\Component\Console\Question\ConfirmationQuestion;
22use Symfony\Component\Console\Question\Question;
23use Symfony\Component\Console\Terminal;
24
25/**
26 * @group tty
27 */
28class QuestionHelperTest extends AbstractQuestionHelperTest
29{
30    public function testAskChoice()
31    {
32        $questionHelper = new QuestionHelper();
33
34        $helperSet = new HelperSet([new FormatterHelper()]);
35        $questionHelper->setHelperSet($helperSet);
36
37        $heroes = ['Superman', 'Batman', 'Spiderman'];
38
39        $inputStream = $this->getInputStream("\n1\n  1  \nFabien\n1\nFabien\n1\n0,2\n 0 , 2  \n\n\n");
40
41        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '2');
42        $question->setMaxAttempts(1);
43        // first answer is an empty answer, we're supposed to receive the default value
44        $this->assertEquals('Spiderman', $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
45
46        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes);
47        $question->setMaxAttempts(1);
48        $this->assertEquals('Batman', $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
49        $this->assertEquals('Batman', $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
50
51        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes);
52        $question->setErrorMessage('Input "%s" is not a superhero!');
53        $question->setMaxAttempts(2);
54        $this->assertEquals('Batman', $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $output = $this->createOutputInterface(), $question));
55
56        rewind($output->getStream());
57        $stream = stream_get_contents($output->getStream());
58        $this->assertStringContainsString('Input "Fabien" is not a superhero!', $stream);
59
60        try {
61            $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '1');
62            $question->setMaxAttempts(1);
63            $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $output = $this->createOutputInterface(), $question);
64            $this->fail();
65        } catch (\InvalidArgumentException $e) {
66            $this->assertEquals('Value "Fabien" is invalid', $e->getMessage());
67        }
68
69        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, null);
70        $question->setMaxAttempts(1);
71        $question->setMultiselect(true);
72
73        $this->assertEquals(['Batman'], $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
74        $this->assertEquals(['Superman', 'Spiderman'], $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
75        $this->assertEquals(['Superman', 'Spiderman'], $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
76
77        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '0,1');
78        $question->setMaxAttempts(1);
79        $question->setMultiselect(true);
80
81        $this->assertEquals(['Superman', 'Batman'], $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
82
83        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, ' 0 , 1 ');
84        $question->setMaxAttempts(1);
85        $question->setMultiselect(true);
86
87        $this->assertEquals(['Superman', 'Batman'], $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
88
89        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, 0);
90        // We are supposed to get the default value since we are not in interactive mode
91        $this->assertEquals('Superman', $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, true), $this->createOutputInterface(), $question));
92    }
93
94    public function testAskChoiceNonInteractive()
95    {
96        $questionHelper = new QuestionHelper();
97
98        $helperSet = new HelperSet([new FormatterHelper()]);
99        $questionHelper->setHelperSet($helperSet);
100        $inputStream = $this->getInputStream("\n1\n  1  \nFabien\n1\nFabien\n1\n0,2\n 0 , 2  \n\n\n");
101
102        $heroes = ['Superman', 'Batman', 'Spiderman'];
103
104        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '0');
105
106        $this->assertSame('Superman', $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question));
107
108        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, 'Batman');
109        $this->assertSame('Batman', $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question));
110
111        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, null);
112        $this->assertNull($questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question));
113
114        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '0');
115        $question->setValidator(null);
116        $this->assertSame('Superman', $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question));
117
118        try {
119            $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, null);
120            $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question);
121        } catch (\InvalidArgumentException $e) {
122            $this->assertSame('Value "" is invalid', $e->getMessage());
123        }
124
125        $question = new ChoiceQuestion('Who are your favorite superheros?', $heroes, '0, 1');
126        $question->setMultiselect(true);
127        $this->assertSame(['Superman', 'Batman'], $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question));
128
129        $question = new ChoiceQuestion('Who are your favorite superheros?', $heroes, '0, 1');
130        $question->setMultiselect(true);
131        $question->setValidator(null);
132        $this->assertSame(['Superman', 'Batman'], $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question));
133
134        $question = new ChoiceQuestion('Who are your favorite superheros?', $heroes, '0, Batman');
135        $question->setMultiselect(true);
136        $this->assertSame(['Superman', 'Batman'], $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question));
137
138        $question = new ChoiceQuestion('Who are your favorite superheros?', $heroes, null);
139        $question->setMultiselect(true);
140        $this->assertNull($questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question));
141
142        $question = new ChoiceQuestion('Who are your favorite superheros?', ['a' => 'Batman', 'b' => 'Superman'], 'a');
143        $this->assertSame('a', $questionHelper->ask($this->createStreamableInputInterfaceMock('', false), $this->createOutputInterface(), $question), 'ChoiceQuestion validator returns the key if it\'s a string');
144
145        try {
146            $question = new ChoiceQuestion('Who are your favorite superheros?', $heroes, '');
147            $question->setMultiselect(true);
148            $questionHelper->ask($this->createStreamableInputInterfaceMock($inputStream, false), $this->createOutputInterface(), $question);
149        } catch (\InvalidArgumentException $e) {
150            $this->assertSame('Value "" is invalid', $e->getMessage());
151        }
152    }
153
154    public function testAsk()
155    {
156        $dialog = new QuestionHelper();
157
158        $inputStream = $this->getInputStream("\n8AM\n");
159
160        $question = new Question('What time is it?', '2PM');
161        $this->assertEquals('2PM', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
162
163        $question = new Question('What time is it?', '2PM');
164        $this->assertEquals('8AM', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $output = $this->createOutputInterface(), $question));
165
166        rewind($output->getStream());
167        $this->assertEquals('What time is it?', stream_get_contents($output->getStream()));
168    }
169
170    public function testAskWithAutocomplete()
171    {
172        if (!Terminal::hasSttyAvailable()) {
173            $this->markTestSkipped('`stty` is required to test autocomplete functionality');
174        }
175
176        // Acm<NEWLINE>
177        // Ac<BACKSPACE><BACKSPACE>s<TAB>Test<NEWLINE>
178        // <NEWLINE>
179        // <UP ARROW><UP ARROW><UP ARROW><NEWLINE>
180        // <UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><TAB>Test<NEWLINE>
181        // <DOWN ARROW><NEWLINE>
182        // S<BACKSPACE><BACKSPACE><DOWN ARROW><DOWN ARROW><NEWLINE>
183        // F00<BACKSPACE><BACKSPACE>oo<TAB><NEWLINE>
184        // F⭐<TAB><BACKSPACE><BACKSPACE>⭐<TAB><NEWLINE>
185        $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");
186
187        $dialog = new QuestionHelper();
188        $helperSet = new HelperSet([new FormatterHelper()]);
189        $dialog->setHelperSet($helperSet);
190
191        $question = new Question('Please select a bundle', 'FrameworkBundle');
192        $question->setAutocompleterValues(['AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle', 'F⭐Y']);
193
194        $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
195        $this->assertEquals('AsseticBundleTest', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
196        $this->assertEquals('FrameworkBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
197        $this->assertEquals('SecurityBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
198        $this->assertEquals('FooBundleTest', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
199        $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
200        $this->assertEquals('AsseticBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
201        $this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
202        $this->assertEquals('F⭐Y', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
203    }
204
205    public function testAskWithAutocompleteWithNonSequentialKeys()
206    {
207        if (!Terminal::hasSttyAvailable()) {
208            $this->markTestSkipped('`stty` is required to test autocomplete functionality');
209        }
210
211        // <UP ARROW><UP ARROW><NEWLINE><DOWN ARROW><DOWN ARROW><NEWLINE>
212        $inputStream = $this->getInputStream("\033[A\033[A\n\033[B\033[B\n");
213
214        $dialog = new QuestionHelper();
215        $dialog->setHelperSet(new HelperSet([new FormatterHelper()]));
216
217        $question = new ChoiceQuestion('Please select a bundle', [1 => 'AcmeDemoBundle', 4 => 'AsseticBundle']);
218        $question->setMaxAttempts(1);
219
220        $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
221        $this->assertEquals('AsseticBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
222    }
223
224    public function testAskWithAutocompleteWithExactMatch()
225    {
226        if (!Terminal::hasSttyAvailable()) {
227            $this->markTestSkipped('`stty` is required to test autocomplete functionality');
228        }
229
230        $inputStream = $this->getInputStream("b\n");
231
232        $possibleChoices = [
233            'a' => 'berlin',
234            'b' => 'copenhagen',
235            'c' => 'amsterdam',
236        ];
237
238        $dialog = new QuestionHelper();
239        $dialog->setHelperSet(new HelperSet([new FormatterHelper()]));
240
241        $question = new ChoiceQuestion('Please select a city', $possibleChoices);
242        $question->setMaxAttempts(1);
243
244        $this->assertSame('b', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
245    }
246
247    public function getInputs()
248    {
249        return [
250            ['$'], // 1 byte character
251            ['¢'], // 2 bytes character
252            ['€'], // 3 bytes character
253            ['��'], // 4 bytes character
254        ];
255    }
256
257    /**
258     * @dataProvider getInputs
259     */
260    public function testAskWithAutocompleteWithMultiByteCharacter($character)
261    {
262        if (!Terminal::hasSttyAvailable()) {
263            $this->markTestSkipped('`stty` is required to test autocomplete functionality');
264        }
265
266        $inputStream = $this->getInputStream("$character\n");
267
268        $possibleChoices = [
269            '$' => '1 byte character',
270            '¢' => '2 bytes character',
271            '€' => '3 bytes character',
272            '��' => '4 bytes character',
273        ];
274
275        $dialog = new QuestionHelper();
276        $dialog->setHelperSet(new HelperSet([new FormatterHelper()]));
277
278        $question = new ChoiceQuestion('Please select a character', $possibleChoices);
279        $question->setMaxAttempts(1);
280
281        $this->assertSame($character, $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
282    }
283
284    public function testAutocompleteWithTrailingBackslash()
285    {
286        if (!Terminal::hasSttyAvailable()) {
287            $this->markTestSkipped('`stty` is required to test autocomplete functionality');
288        }
289
290        $inputStream = $this->getInputStream('E');
291
292        $dialog = new QuestionHelper();
293        $helperSet = new HelperSet([new FormatterHelper()]);
294        $dialog->setHelperSet($helperSet);
295
296        $question = new Question('');
297        $expectedCompletion = 'ExampleNamespace\\';
298        $question->setAutocompleterValues([$expectedCompletion]);
299
300        $output = $this->createOutputInterface();
301        $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $output, $question);
302
303        $outputStream = $output->getStream();
304        rewind($outputStream);
305        $actualOutput = stream_get_contents($outputStream);
306
307        // Shell control (esc) sequences are not so important: we only care that
308        // <hl> tag is interpreted correctly and replaced
309        $irrelevantEscSequences = [
310            "\0337" => '', // Save cursor position
311            "\0338" => '', // Restore cursor position
312            "\033[K" => '', // Clear line from cursor till the end
313        ];
314
315        $importantActualOutput = strtr($actualOutput, $irrelevantEscSequences);
316
317        // Remove colors (e.g. "\033[30m", "\033[31;41m")
318        $importantActualOutput = preg_replace('/\033\[\d+(;\d+)?m/', '', $importantActualOutput);
319
320        $this->assertEquals($expectedCompletion, $importantActualOutput);
321    }
322
323    public function testAskHiddenResponse()
324    {
325        if ('\\' === \DIRECTORY_SEPARATOR) {
326            $this->markTestSkipped('This test is not supported on Windows');
327        }
328
329        $dialog = new QuestionHelper();
330
331        $question = new Question('What time is it?');
332        $question->setHidden(true);
333
334        $this->assertEquals('8AM', $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream("8AM\n")), $this->createOutputInterface(), $question));
335    }
336
337    /**
338     * @dataProvider getAskConfirmationData
339     */
340    public function testAskConfirmation($question, $expected, $default = true)
341    {
342        $dialog = new QuestionHelper();
343
344        $inputStream = $this->getInputStream($question."\n");
345        $question = new ConfirmationQuestion('Do you like French fries?', $default);
346        $this->assertEquals($expected, $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question), 'confirmation question should '.($expected ? 'pass' : 'cancel'));
347    }
348
349    public function getAskConfirmationData()
350    {
351        return [
352            ['', true],
353            ['', false, false],
354            ['y', true],
355            ['yes', true],
356            ['n', false],
357            ['no', false],
358        ];
359    }
360
361    public function testAskConfirmationWithCustomTrueAnswer()
362    {
363        $dialog = new QuestionHelper();
364
365        $inputStream = $this->getInputStream("j\ny\n");
366        $question = new ConfirmationQuestion('Do you like French fries?', false, '/^(j|y)/i');
367        $this->assertTrue($dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
368        $question = new ConfirmationQuestion('Do you like French fries?', false, '/^(j|y)/i');
369        $this->assertTrue($dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
370    }
371
372    public function testAskAndValidate()
373    {
374        $dialog = new QuestionHelper();
375        $helperSet = new HelperSet([new FormatterHelper()]);
376        $dialog->setHelperSet($helperSet);
377
378        $error = 'This is not a color!';
379        $validator = function ($color) use ($error) {
380            if (!\in_array($color, ['white', 'black'])) {
381                throw new \InvalidArgumentException($error);
382            }
383
384            return $color;
385        };
386
387        $question = new Question('What color was the white horse of Henry IV?', 'white');
388        $question->setValidator($validator);
389        $question->setMaxAttempts(2);
390
391        $inputStream = $this->getInputStream("\nblack\n");
392        $this->assertEquals('white', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
393        $this->assertEquals('black', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
394
395        try {
396            $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream("green\nyellow\norange\n")), $this->createOutputInterface(), $question);
397            $this->fail();
398        } catch (\InvalidArgumentException $e) {
399            $this->assertEquals($error, $e->getMessage());
400        }
401    }
402
403    /**
404     * @dataProvider simpleAnswerProvider
405     */
406    public function testSelectChoiceFromSimpleChoices($providedAnswer, $expectedValue)
407    {
408        $possibleChoices = [
409            'My environment 1',
410            'My environment 2',
411            'My environment 3',
412        ];
413
414        $dialog = new QuestionHelper();
415        $helperSet = new HelperSet([new FormatterHelper()]);
416        $dialog->setHelperSet($helperSet);
417
418        $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices);
419        $question->setMaxAttempts(1);
420        $answer = $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream($providedAnswer."\n")), $this->createOutputInterface(), $question);
421
422        $this->assertSame($expectedValue, $answer);
423    }
424
425    public function simpleAnswerProvider()
426    {
427        return [
428            [0, 'My environment 1'],
429            [1, 'My environment 2'],
430            [2, 'My environment 3'],
431            ['My environment 1', 'My environment 1'],
432            ['My environment 2', 'My environment 2'],
433            ['My environment 3', 'My environment 3'],
434        ];
435    }
436
437    /**
438     * @dataProvider specialCharacterInMultipleChoice
439     */
440    public function testSpecialCharacterChoiceFromMultipleChoiceList($providedAnswer, $expectedValue)
441    {
442        $possibleChoices = [
443            '.',
444            'src',
445        ];
446
447        $dialog = new QuestionHelper();
448        $inputStream = $this->getInputStream($providedAnswer."\n");
449        $helperSet = new HelperSet([new FormatterHelper()]);
450        $dialog->setHelperSet($helperSet);
451
452        $question = new ChoiceQuestion('Please select the directory', $possibleChoices);
453        $question->setMaxAttempts(1);
454        $question->setMultiselect(true);
455        $answer = $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question);
456
457        $this->assertSame($expectedValue, $answer);
458    }
459
460    public function specialCharacterInMultipleChoice()
461    {
462        return [
463            ['.', ['.']],
464            ['., src', ['.', 'src']],
465        ];
466    }
467
468    /**
469     * @dataProvider mixedKeysChoiceListAnswerProvider
470     */
471    public function testChoiceFromChoicelistWithMixedKeys($providedAnswer, $expectedValue)
472    {
473        $possibleChoices = [
474            '0' => 'No environment',
475            '1' => 'My environment 1',
476            'env_2' => 'My environment 2',
477            3 => 'My environment 3',
478        ];
479
480        $dialog = new QuestionHelper();
481        $helperSet = new HelperSet([new FormatterHelper()]);
482        $dialog->setHelperSet($helperSet);
483
484        $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices);
485        $question->setMaxAttempts(1);
486        $answer = $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream($providedAnswer."\n")), $this->createOutputInterface(), $question);
487
488        $this->assertSame($expectedValue, $answer);
489    }
490
491    public function mixedKeysChoiceListAnswerProvider()
492    {
493        return [
494            ['0', '0'],
495            ['No environment', '0'],
496            ['1', '1'],
497            ['env_2', 'env_2'],
498            [3, '3'],
499            ['My environment 1', '1'],
500        ];
501    }
502
503    /**
504     * @dataProvider answerProvider
505     */
506    public function testSelectChoiceFromChoiceList($providedAnswer, $expectedValue)
507    {
508        $possibleChoices = [
509            'env_1' => 'My environment 1',
510            'env_2' => 'My environment',
511            'env_3' => 'My environment',
512        ];
513
514        $dialog = new QuestionHelper();
515        $helperSet = new HelperSet([new FormatterHelper()]);
516        $dialog->setHelperSet($helperSet);
517
518        $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices);
519        $question->setMaxAttempts(1);
520        $answer = $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream($providedAnswer."\n")), $this->createOutputInterface(), $question);
521
522        $this->assertSame($expectedValue, $answer);
523    }
524
525    public function testAmbiguousChoiceFromChoicelist()
526    {
527        $this->expectException('InvalidArgumentException');
528        $this->expectExceptionMessage('The provided answer is ambiguous. Value should be one of "env_2" or "env_3".');
529        $possibleChoices = [
530            'env_1' => 'My first environment',
531            'env_2' => 'My environment',
532            'env_3' => 'My environment',
533        ];
534
535        $dialog = new QuestionHelper();
536        $helperSet = new HelperSet([new FormatterHelper()]);
537        $dialog->setHelperSet($helperSet);
538
539        $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices);
540        $question->setMaxAttempts(1);
541
542        $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream("My environment\n")), $this->createOutputInterface(), $question);
543    }
544
545    public function answerProvider()
546    {
547        return [
548            ['env_1', 'env_1'],
549            ['env_2', 'env_2'],
550            ['env_3', 'env_3'],
551            ['My environment 1', 'env_1'],
552        ];
553    }
554
555    public function testNoInteraction()
556    {
557        $dialog = new QuestionHelper();
558        $question = new Question('Do you have a job?', 'not yet');
559        $this->assertEquals('not yet', $dialog->ask($this->createStreamableInputInterfaceMock(null, false), $this->createOutputInterface(), $question));
560    }
561
562    /**
563     * @requires function mb_strwidth
564     */
565    public function testChoiceOutputFormattingQuestionForUtf8Keys()
566    {
567        $question = 'Lorem ipsum?';
568        $possibleChoices = [
569            'foo' => 'foo',
570            'żółw' => 'bar',
571            'łabądź' => 'baz',
572        ];
573        $outputShown = [
574            $question,
575            '  [<info>foo   </info>] foo',
576            '  [<info>żółw  </info>] bar',
577            '  [<info>łabądź</info>] baz',
578        ];
579        $output = $this->getMockBuilder('\Symfony\Component\Console\Output\OutputInterface')->getMock();
580        $output->method('getFormatter')->willReturn(new OutputFormatter());
581
582        $dialog = new QuestionHelper();
583        $helperSet = new HelperSet([new FormatterHelper()]);
584        $dialog->setHelperSet($helperSet);
585
586        $output->expects($this->once())->method('writeln')->with($this->equalTo($outputShown));
587
588        $question = new ChoiceQuestion($question, $possibleChoices, 'foo');
589        $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream("\n")), $output, $question);
590    }
591
592    /**
593     * @group legacy
594     */
595    public function testLegacyAskChoice()
596    {
597        $questionHelper = new QuestionHelper();
598
599        $helperSet = new HelperSet([new FormatterHelper()]);
600        $questionHelper->setHelperSet($helperSet);
601
602        $heroes = ['Superman', 'Batman', 'Spiderman'];
603
604        $questionHelper->setInputStream($this->getInputStream("\n1\n  1  \nFabien\n1\nFabien\n1\n0,2\n 0 , 2  \n\n\n"));
605
606        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '2');
607        $question->setMaxAttempts(1);
608        // first answer is an empty answer, we're supposed to receive the default value
609        $this->assertEquals('Spiderman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
610
611        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes);
612        $question->setMaxAttempts(1);
613        $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
614        $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
615
616        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes);
617        $question->setErrorMessage('Input "%s" is not a superhero!');
618        $question->setMaxAttempts(2);
619        $this->assertEquals('Batman', $questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question));
620
621        rewind($output->getStream());
622        $stream = stream_get_contents($output->getStream());
623        $this->assertStringContainsString('Input "Fabien" is not a superhero!', $stream);
624
625        try {
626            $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '1');
627            $question->setMaxAttempts(1);
628            $questionHelper->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question);
629            $this->fail();
630        } catch (\InvalidArgumentException $e) {
631            $this->assertEquals('Value "Fabien" is invalid', $e->getMessage());
632        }
633
634        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, null);
635        $question->setMaxAttempts(1);
636        $question->setMultiselect(true);
637
638        $this->assertEquals(['Batman'], $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
639        $this->assertEquals(['Superman', 'Spiderman'], $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
640        $this->assertEquals(['Superman', 'Spiderman'], $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
641
642        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, '0,1');
643        $question->setMaxAttempts(1);
644        $question->setMultiselect(true);
645
646        $this->assertEquals(['Superman', 'Batman'], $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
647
648        $question = new ChoiceQuestion('What is your favorite superhero?', $heroes, ' 0 , 1 ');
649        $question->setMaxAttempts(1);
650        $question->setMultiselect(true);
651
652        $this->assertEquals(['Superman', 'Batman'], $questionHelper->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
653    }
654
655    /**
656     * @group legacy
657     */
658    public function testLegacyAsk()
659    {
660        $dialog = new QuestionHelper();
661
662        $dialog->setInputStream($this->getInputStream("\n8AM\n"));
663
664        $question = new Question('What time is it?', '2PM');
665        $this->assertEquals('2PM', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
666
667        $question = new Question('What time is it?', '2PM');
668        $this->assertEquals('8AM', $dialog->ask($this->createInputInterfaceMock(), $output = $this->createOutputInterface(), $question));
669
670        rewind($output->getStream());
671        $this->assertEquals('What time is it?', stream_get_contents($output->getStream()));
672    }
673
674    /**
675     * @group legacy
676     */
677    public function testLegacyAskWithAutocomplete()
678    {
679        if (!Terminal::hasSttyAvailable()) {
680            $this->markTestSkipped('`stty` is required to test autocomplete functionality');
681        }
682
683        // Acm<NEWLINE>
684        // Ac<BACKSPACE><BACKSPACE>s<TAB>Test<NEWLINE>
685        // <NEWLINE>
686        // <UP ARROW><UP ARROW><UP ARROW><NEWLINE>
687        // <UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><TAB>Test<NEWLINE>
688        // <DOWN ARROW><NEWLINE>
689        // S<BACKSPACE><BACKSPACE><DOWN ARROW><DOWN ARROW><NEWLINE>
690        // F00<BACKSPACE><BACKSPACE>oo<TAB><NEWLINE>
691        // F⭐<TAB><BACKSPACE><BACKSPACE>⭐<TAB><NEWLINE>
692        $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");
693
694        $dialog = new QuestionHelper();
695        $dialog->setInputStream($inputStream);
696        $helperSet = new HelperSet([new FormatterHelper()]);
697        $dialog->setHelperSet($helperSet);
698
699        $question = new Question('Please select a bundle', 'FrameworkBundle');
700        $question->setAutocompleterValues(['AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle', 'F⭐Y']);
701
702        $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
703        $this->assertEquals('AsseticBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
704        $this->assertEquals('FrameworkBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
705        $this->assertEquals('SecurityBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
706        $this->assertEquals('FooBundleTest', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
707        $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
708        $this->assertEquals('AsseticBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
709        $this->assertEquals('FooBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
710        $this->assertEquals('F⭐Y', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
711    }
712
713    /**
714     * @group legacy
715     */
716    public function testLegacyAskWithAutocompleteWithNonSequentialKeys()
717    {
718        if (!Terminal::hasSttyAvailable()) {
719            $this->markTestSkipped('`stty` is required to test autocomplete functionality');
720        }
721
722        // <UP ARROW><UP ARROW><NEWLINE><DOWN ARROW><DOWN ARROW><NEWLINE>
723        $inputStream = $this->getInputStream("\033[A\033[A\n\033[B\033[B\n");
724
725        $dialog = new QuestionHelper();
726        $dialog->setInputStream($inputStream);
727        $dialog->setHelperSet(new HelperSet([new FormatterHelper()]));
728
729        $question = new ChoiceQuestion('Please select a bundle', [1 => 'AcmeDemoBundle', 4 => 'AsseticBundle']);
730        $question->setMaxAttempts(1);
731
732        $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
733        $this->assertEquals('AsseticBundle', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
734    }
735
736    /**
737     * @group legacy
738     */
739    public function testLegacyAskHiddenResponse()
740    {
741        if ('\\' === \DIRECTORY_SEPARATOR) {
742            $this->markTestSkipped('This test is not supported on Windows');
743        }
744
745        $dialog = new QuestionHelper();
746        $dialog->setInputStream($this->getInputStream("8AM\n"));
747
748        $question = new Question('What time is it?');
749        $question->setHidden(true);
750
751        $this->assertEquals('8AM', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
752    }
753
754    /**
755     * @group legacy
756     * @dataProvider getAskConfirmationData
757     */
758    public function testLegacyAskConfirmation($question, $expected, $default = true)
759    {
760        $dialog = new QuestionHelper();
761
762        $dialog->setInputStream($this->getInputStream($question."\n"));
763        $question = new ConfirmationQuestion('Do you like French fries?', $default);
764        $this->assertEquals($expected, $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question), 'confirmation question should '.($expected ? 'pass' : 'cancel'));
765    }
766
767    /**
768     * @group legacy
769     */
770    public function testLegacyAskConfirmationWithCustomTrueAnswer()
771    {
772        $dialog = new QuestionHelper();
773
774        $dialog->setInputStream($this->getInputStream("j\ny\n"));
775        $question = new ConfirmationQuestion('Do you like French fries?', false, '/^(j|y)/i');
776        $this->assertTrue($dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
777        $question = new ConfirmationQuestion('Do you like French fries?', false, '/^(j|y)/i');
778        $this->assertTrue($dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
779    }
780
781    /**
782     * @group legacy
783     */
784    public function testLegacyAskAndValidate()
785    {
786        $dialog = new QuestionHelper();
787        $helperSet = new HelperSet([new FormatterHelper()]);
788        $dialog->setHelperSet($helperSet);
789
790        $error = 'This is not a color!';
791        $validator = function ($color) use ($error) {
792            if (!\in_array($color, ['white', 'black'])) {
793                throw new \InvalidArgumentException($error);
794            }
795
796            return $color;
797        };
798
799        $question = new Question('What color was the white horse of Henry IV?', 'white');
800        $question->setValidator($validator);
801        $question->setMaxAttempts(2);
802
803        $dialog->setInputStream($this->getInputStream("\nblack\n"));
804        $this->assertEquals('white', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
805        $this->assertEquals('black', $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question));
806
807        $dialog->setInputStream($this->getInputStream("green\nyellow\norange\n"));
808        try {
809            $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question);
810            $this->fail();
811        } catch (\InvalidArgumentException $e) {
812            $this->assertEquals($error, $e->getMessage());
813        }
814    }
815
816    /**
817     * @group legacy
818     * @dataProvider simpleAnswerProvider
819     */
820    public function testLegacySelectChoiceFromSimpleChoices($providedAnswer, $expectedValue)
821    {
822        $possibleChoices = [
823            'My environment 1',
824            'My environment 2',
825            'My environment 3',
826        ];
827
828        $dialog = new QuestionHelper();
829        $dialog->setInputStream($this->getInputStream($providedAnswer."\n"));
830        $helperSet = new HelperSet([new FormatterHelper()]);
831        $dialog->setHelperSet($helperSet);
832
833        $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices);
834        $question->setMaxAttempts(1);
835        $answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question);
836
837        $this->assertSame($expectedValue, $answer);
838    }
839
840    /**
841     * @group legacy
842     * @dataProvider mixedKeysChoiceListAnswerProvider
843     */
844    public function testLegacyChoiceFromChoicelistWithMixedKeys($providedAnswer, $expectedValue)
845    {
846        $possibleChoices = [
847            '0' => 'No environment',
848            '1' => 'My environment 1',
849            'env_2' => 'My environment 2',
850            3 => 'My environment 3',
851        ];
852
853        $dialog = new QuestionHelper();
854        $dialog->setInputStream($this->getInputStream($providedAnswer."\n"));
855        $helperSet = new HelperSet([new FormatterHelper()]);
856        $dialog->setHelperSet($helperSet);
857
858        $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices);
859        $question->setMaxAttempts(1);
860        $answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question);
861
862        $this->assertSame($expectedValue, $answer);
863    }
864
865    /**
866     * @group legacy
867     * @dataProvider answerProvider
868     */
869    public function testLegacySelectChoiceFromChoiceList($providedAnswer, $expectedValue)
870    {
871        $possibleChoices = [
872            'env_1' => 'My environment 1',
873            'env_2' => 'My environment',
874            'env_3' => 'My environment',
875        ];
876
877        $dialog = new QuestionHelper();
878        $dialog->setInputStream($this->getInputStream($providedAnswer."\n"));
879        $helperSet = new HelperSet([new FormatterHelper()]);
880        $dialog->setHelperSet($helperSet);
881
882        $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices);
883        $question->setMaxAttempts(1);
884        $answer = $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question);
885
886        $this->assertSame($expectedValue, $answer);
887    }
888
889    /**
890     * @group legacy
891     */
892    public function testLegacyAmbiguousChoiceFromChoicelist()
893    {
894        $this->expectException('InvalidArgumentException');
895        $this->expectExceptionMessage('The provided answer is ambiguous. Value should be one of "env_2" or "env_3".');
896        $possibleChoices = [
897            'env_1' => 'My first environment',
898            'env_2' => 'My environment',
899            'env_3' => 'My environment',
900        ];
901
902        $dialog = new QuestionHelper();
903        $dialog->setInputStream($this->getInputStream("My environment\n"));
904        $helperSet = new HelperSet([new FormatterHelper()]);
905        $dialog->setHelperSet($helperSet);
906
907        $question = new ChoiceQuestion('Please select the environment to load', $possibleChoices);
908        $question->setMaxAttempts(1);
909
910        $dialog->ask($this->createInputInterfaceMock(), $this->createOutputInterface(), $question);
911    }
912
913    /**
914     * @requires function mb_strwidth
915     * @group legacy
916     */
917    public function testLegacyChoiceOutputFormattingQuestionForUtf8Keys()
918    {
919        $question = 'Lorem ipsum?';
920        $possibleChoices = [
921            'foo' => 'foo',
922            'żółw' => 'bar',
923            'łabądź' => 'baz',
924        ];
925        $outputShown = [
926            $question,
927            '  [<info>foo   </info>] foo',
928            '  [<info>żółw  </info>] bar',
929            '  [<info>łabądź</info>] baz',
930        ];
931        $output = $this->getMockBuilder('\Symfony\Component\Console\Output\OutputInterface')->getMock();
932        $output->method('getFormatter')->willReturn(new OutputFormatter());
933
934        $dialog = new QuestionHelper();
935        $dialog->setInputStream($this->getInputStream("\n"));
936        $helperSet = new HelperSet([new FormatterHelper()]);
937        $dialog->setHelperSet($helperSet);
938
939        $output->expects($this->once())->method('writeln')->with($this->equalTo($outputShown));
940
941        $question = new ChoiceQuestion($question, $possibleChoices, 'foo');
942        $dialog->ask($this->createInputInterfaceMock(), $output, $question);
943    }
944
945    public function testAskThrowsExceptionOnMissingInput()
946    {
947        $this->expectException('Symfony\Component\Console\Exception\RuntimeException');
948        $this->expectExceptionMessage('Aborted.');
949        $dialog = new QuestionHelper();
950        $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), new Question('What\'s your name?'));
951    }
952
953    public function testAskThrowsExceptionOnMissingInputForChoiceQuestion()
954    {
955        $this->expectException('Symfony\Component\Console\Exception\RuntimeException');
956        $this->expectExceptionMessage('Aborted.');
957        $dialog = new QuestionHelper();
958        $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), new ChoiceQuestion('Choice', ['a', 'b']));
959    }
960
961    public function testAskThrowsExceptionOnMissingInputWithValidator()
962    {
963        $this->expectException('Symfony\Component\Console\Exception\RuntimeException');
964        $this->expectExceptionMessage('Aborted.');
965        $dialog = new QuestionHelper();
966
967        $question = new Question('What\'s your name?');
968        $question->setValidator(function ($value) {
969            if (!$value) {
970                throw new \Exception('A value is required.');
971            }
972        });
973
974        $dialog->ask($this->createStreamableInputInterfaceMock($this->getInputStream('')), $this->createOutputInterface(), $question);
975    }
976
977    public function testEmptyChoices()
978    {
979        $this->expectException('LogicException');
980        $this->expectExceptionMessage('Choice question must have at least 1 choice available.');
981        new ChoiceQuestion('Question', [], 'irrelevant');
982    }
983
984    public function testTraversableAutocomplete()
985    {
986        if (!Terminal::hasSttyAvailable()) {
987            $this->markTestSkipped('`stty` is required to test autocomplete functionality');
988        }
989
990        // Acm<NEWLINE>
991        // Ac<BACKSPACE><BACKSPACE>s<TAB>Test<NEWLINE>
992        // <NEWLINE>
993        // <UP ARROW><UP ARROW><NEWLINE>
994        // <UP ARROW><UP ARROW><UP ARROW><UP ARROW><UP ARROW><TAB>Test<NEWLINE>
995        // <DOWN ARROW><NEWLINE>
996        // S<BACKSPACE><BACKSPACE><DOWN ARROW><DOWN ARROW><NEWLINE>
997        // F00<BACKSPACE><BACKSPACE>oo<TAB><NEWLINE>
998        $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");
999
1000        $dialog = new QuestionHelper();
1001        $helperSet = new HelperSet([new FormatterHelper()]);
1002        $dialog->setHelperSet($helperSet);
1003
1004        $question = new Question('Please select a bundle', 'FrameworkBundle');
1005        $question->setAutocompleterValues(new AutocompleteValues(['irrelevant' => 'AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle']));
1006
1007        $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1008        $this->assertEquals('AsseticBundleTest', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1009        $this->assertEquals('FrameworkBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1010        $this->assertEquals('SecurityBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1011        $this->assertEquals('FooBundleTest', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1012        $this->assertEquals('AcmeDemoBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1013        $this->assertEquals('AsseticBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1014        $this->assertEquals('FooBundle', $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1015    }
1016
1017    public function testDisableStty()
1018    {
1019        if (!Terminal::hasSttyAvailable()) {
1020            $this->markTestSkipped('`stty` is required to test autocomplete functionality');
1021        }
1022
1023        $this->expectException(InvalidArgumentException::class);
1024        $this->expectExceptionMessage('invalid');
1025
1026        QuestionHelper::disableStty();
1027        $dialog = new QuestionHelper();
1028        $dialog->setHelperSet(new HelperSet([new FormatterHelper()]));
1029
1030        $question = new ChoiceQuestion('Please select a bundle', [1 => 'AcmeDemoBundle', 4 => 'AsseticBundle']);
1031        $question->setMaxAttempts(1);
1032
1033        // <UP ARROW><UP ARROW><NEWLINE><DOWN ARROW><DOWN ARROW><NEWLINE>
1034        // Gives `AcmeDemoBundle` with stty
1035        $inputStream = $this->getInputStream("\033[A\033[A\n\033[B\033[B\n");
1036
1037        try {
1038            $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question);
1039        } finally {
1040            $reflection = new \ReflectionProperty(QuestionHelper::class, 'stty');
1041            $reflection->setAccessible(true);
1042            $reflection->setValue(null, true);
1043        }
1044    }
1045
1046    public function testTraversableMultiselectAutocomplete()
1047    {
1048        // <NEWLINE>
1049        // F<TAB><NEWLINE>
1050        // A<3x UP ARROW><TAB>,F<TAB><NEWLINE>
1051        // F00<BACKSPACE><BACKSPACE>o<TAB>,A<DOWN ARROW>,<SPACE>SecurityBundle<NEWLINE>
1052        // Acme<TAB>,<SPACE>As<TAB><29x BACKSPACE>S<TAB><NEWLINE>
1053        // Ac<TAB>,As<TAB><3x BACKSPACE>d<TAB><NEWLINE>
1054        $inputStream = $this->getInputStream("\nF\t\nA\033[A\033[A\033[A\t,F\t\nF00\177\177o\t,A\033[B\t, SecurityBundle\nAcme\t, As\t\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177\177S\t\nAc\t,As\t\177\177\177d\t\n");
1055
1056        $dialog = new QuestionHelper();
1057        $helperSet = new HelperSet([new FormatterHelper()]);
1058        $dialog->setHelperSet($helperSet);
1059
1060        $question = new ChoiceQuestion(
1061            'Please select a bundle (defaults to AcmeDemoBundle and AsseticBundle)',
1062            ['AcmeDemoBundle', 'AsseticBundle', 'SecurityBundle', 'FooBundle'],
1063            '0,1'
1064        );
1065
1066        // This tests that autocomplete works for all multiselect choices entered by the user
1067        $question->setMultiselect(true);
1068
1069        $this->assertEquals(['AcmeDemoBundle', 'AsseticBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1070        $this->assertEquals(['FooBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1071        $this->assertEquals(['AsseticBundle', 'FooBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1072        $this->assertEquals(['FooBundle', 'AsseticBundle', 'SecurityBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1073        $this->assertEquals(['SecurityBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1074        $this->assertEquals(['AcmeDemoBundle', 'AsseticBundle'], $dialog->ask($this->createStreamableInputInterfaceMock($inputStream), $this->createOutputInterface(), $question));
1075    }
1076
1077    protected function getInputStream($input)
1078    {
1079        $stream = fopen('php://memory', 'r+', false);
1080        fwrite($stream, $input);
1081        rewind($stream);
1082
1083        return $stream;
1084    }
1085
1086    protected function createOutputInterface()
1087    {
1088        return new StreamOutput(fopen('php://memory', 'r+', false));
1089    }
1090
1091    protected function createInputInterfaceMock($interactive = true)
1092    {
1093        $mock = $this->getMockBuilder('Symfony\Component\Console\Input\InputInterface')->getMock();
1094        $mock->expects($this->any())
1095            ->method('isInteractive')
1096            ->willReturn($interactive);
1097
1098        return $mock;
1099    }
1100}
1101
1102class AutocompleteValues implements \IteratorAggregate
1103{
1104    private $values;
1105
1106    public function __construct(array $values)
1107    {
1108        $this->values = $values;
1109    }
1110
1111    public function getIterator()
1112    {
1113        return new \ArrayIterator($this->values);
1114    }
1115}
1116