18b576797b9ea38db0247e1d8c4c6a45eead6d54
[yaffs-website] / vendor / consolidation / output-formatters / tests / testFormatters.php
1 <?php
2 namespace Consolidation\OutputFormatters;
3
4 use Consolidation\TestUtils\PropertyListWithCsvCells;
5 use Consolidation\TestUtils\RowsOfFieldsWithAlternatives;
6 use Consolidation\OutputFormatters\Options\FormatterOptions;
7 use Consolidation\OutputFormatters\StructuredData\AssociativeList;
8 use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
9 use Consolidation\OutputFormatters\StructuredData\PropertyList;
10 use Symfony\Component\Console\Output\BufferedOutput;
11 use Symfony\Component\Console\Output\OutputInterface;
12 use Symfony\Component\Console\Input\InputInterface;
13 use Symfony\Component\Console\Input\StringInput;
14 use Symfony\Component\Console\Input\InputOption;
15 use Symfony\Component\Console\Input\InputArgument;
16 use Symfony\Component\Console\Input\InputDefinition;
17
18 class FormattersTests extends \PHPUnit_Framework_TestCase
19 {
20     protected $formatterManager;
21
22     function setup() {
23         $this->formatterManager = new FormatterManager();
24     }
25
26     function assertFormattedOutputMatches($expected, $format, $data, FormatterOptions $options = null, $userOptions = []) {
27         if (!$options) {
28             $options = new FormatterOptions();
29         }
30         $options->setOptions($userOptions);
31         $output = new BufferedOutput();
32         $this->formatterManager->write($output, $format, $data, $options);
33         $actual = preg_replace('#[ \t]*$#sm', '', $output->fetch());
34         $this->assertEquals(rtrim($expected), rtrim($actual));
35     }
36
37     function testSimpleYaml()
38     {
39         $data = [
40             'one' => 'a',
41             'two' => 'b',
42             'three' => 'c',
43         ];
44
45         $expected = <<<EOT
46 one: a
47 two: b
48 three: c
49 EOT;
50
51         $this->assertFormattedOutputMatches($expected, 'yaml', $data);
52     }
53
54     function testNestedYaml()
55     {
56         $data = [
57             'one' => [
58                 'i' => ['a', 'b', 'c'],
59             ],
60             'two' => [
61                 'ii' => ['q', 'r', 's'],
62             ],
63             'three' => [
64                 'iii' => ['t', 'u', 'v'],
65             ],
66         ];
67
68         $expected = <<<EOT
69 one:
70   i:
71     - a
72     - b
73     - c
74 two:
75   ii:
76     - q
77     - r
78     - s
79 three:
80   iii:
81     - t
82     - u
83     - v
84 EOT;
85
86         $this->assertFormattedOutputMatches($expected, 'yaml', $data);
87     }
88
89     function testSimpleJson()
90     {
91         $data = [
92             'one' => 'a',
93             'two' => 'b',
94             'three' => 'c',
95         ];
96
97         $expected = <<<EOT
98 {
99     "one": "a",
100     "two": "b",
101     "three": "c"
102 }
103 EOT;
104
105         $this->assertFormattedOutputMatches($expected, 'json', $data);
106     }
107
108     function testSerializeFormat()
109     {
110         $data = [
111             'one' => 'a',
112             'two' => 'b',
113             'three' => 'c',
114         ];
115
116         $expected = 'a:3:{s:3:"one";s:1:"a";s:3:"two";s:1:"b";s:5:"three";s:1:"c";}';
117
118         $this->assertFormattedOutputMatches($expected, 'php', $data);
119     }
120
121     function testNestedJson()
122     {
123         $data = [
124             'one' => [
125                 'i' => ['a', 'b', 'c'],
126             ],
127             'two' => [
128                 'ii' => ['q', 'r', 's'],
129             ],
130             'three' => [
131                 'iii' => ['t', 'u', 'v'],
132             ],
133         ];
134
135         $expected = <<<EOT
136 {
137     "one": {
138         "i": [
139             "a",
140             "b",
141             "c"
142         ]
143     },
144     "two": {
145         "ii": [
146             "q",
147             "r",
148             "s"
149         ]
150     },
151     "three": {
152         "iii": [
153             "t",
154             "u",
155             "v"
156         ]
157     }
158 }
159 EOT;
160
161         $this->assertFormattedOutputMatches($expected, 'json', $data);
162     }
163
164     function testSimplePrintR()
165     {
166         $data = [
167             'one' => 'a',
168             'two' => 'b',
169             'three' => 'c',
170         ];
171
172         $expected = <<<EOT
173 Array
174 (
175     [one] => a
176     [two] => b
177     [three] => c
178 )
179 EOT;
180
181         $this->assertFormattedOutputMatches($expected, 'print-r', $data);
182     }
183
184     function testNestedPrintR()
185     {
186         $data = [
187             'one' => [
188                 'i' => ['a', 'b', 'c'],
189             ],
190             'two' => [
191                 'ii' => ['q', 'r', 's'],
192             ],
193             'three' => [
194                 'iii' => ['t', 'u', 'v'],
195             ],
196         ];
197
198         $expected = <<<EOT
199 Array
200 (
201     [one] => Array
202         (
203             [i] => Array
204                 (
205                     [0] => a
206                     [1] => b
207                     [2] => c
208                 )
209
210         )
211
212     [two] => Array
213         (
214             [ii] => Array
215                 (
216                     [0] => q
217                     [1] => r
218                     [2] => s
219                 )
220
221         )
222
223     [three] => Array
224         (
225             [iii] => Array
226                 (
227                     [0] => t
228                     [1] => u
229                     [2] => v
230                 )
231
232         )
233
234 )
235 EOT;
236
237         $this->assertFormattedOutputMatches($expected, 'print-r', $data);
238     }
239
240     function testSimpleVarExport()
241     {
242         $data = [
243             'one' => 'a',
244             'two' => 'b',
245             'three' => 'c',
246         ];
247
248         $expected = <<<EOT
249 array (
250   'one' => 'a',
251   'two' => 'b',
252   'three' => 'c',
253 )
254 EOT;
255
256         $this->assertFormattedOutputMatches($expected, 'var_export', $data);
257     }
258
259     function testNestedVarExport()
260     {
261         $data = [
262             'one' => [
263                 'i' => ['a', 'b', 'c'],
264             ],
265             'two' => [
266                 'ii' => ['q', 'r', 's'],
267             ],
268             'three' => [
269                 'iii' => ['t', 'u', 'v'],
270             ],
271         ];
272
273         $expected = <<<EOT
274 array (
275   'one' =>
276   array (
277     'i' =>
278     array (
279       0 => 'a',
280       1 => 'b',
281       2 => 'c',
282     ),
283   ),
284   'two' =>
285   array (
286     'ii' =>
287     array (
288       0 => 'q',
289       1 => 'r',
290       2 => 's',
291     ),
292   ),
293   'three' =>
294   array (
295     'iii' =>
296     array (
297       0 => 't',
298       1 => 'u',
299       2 => 'v',
300     ),
301   ),
302 )
303 EOT;
304
305         $this->assertFormattedOutputMatches($expected, 'var_export', $data);
306     }
307
308     function testList()
309     {
310         $data = [
311             'one' => 'a',
312             'two' => 'b',
313             'three' => 'c',
314         ];
315
316         $expected = <<<EOT
317 a
318 b
319 c
320 EOT;
321
322         $this->assertFormattedOutputMatches($expected, 'list', $data);
323     }
324
325     /**
326      * @expectedException \Consolidation\OutputFormatters\Exception\UnknownFormatException
327      * @expectedExceptionCode 1
328      * @expectedExceptionMessage The requested format, 'no-such-format', is not available.
329      */
330     function testBadFormat()
331     {
332         $this->assertFormattedOutputMatches('Will fail, not return', 'no-such-format', ['a' => 'b']);
333     }
334
335     /**
336      * @expectedException \Consolidation\OutputFormatters\Exception\IncompatibleDataException
337      * @expectedExceptionCode 1
338      * @expectedExceptionMessage Data provided to Consolidation\OutputFormatters\Formatters\CsvFormatter must be one of an instance of Consolidation\OutputFormatters\StructuredData\RowsOfFields, an instance of Consolidation\OutputFormatters\StructuredData\PropertyList or an array. Instead, a string was provided.
339      */
340     function testBadDataTypeForCsv()
341     {
342         $this->assertFormattedOutputMatches('Will fail, not return', 'csv', 'String cannot be converted to csv');
343     }
344
345     /**
346      * @expectedException \Consolidation\OutputFormatters\Exception\IncompatibleDataException
347      * @expectedExceptionCode 1
348      * @expectedExceptionMessage Data provided to Consolidation\OutputFormatters\Formatters\JsonFormatter must be an array. Instead, a string was provided.
349      */
350     function testBadDataTypeForJson()
351     {
352         $this->assertFormattedOutputMatches('Will fail, not return', 'json', 'String cannot be converted to json');
353     }
354
355     function testNoFormatterSelected()
356     {
357         $data = 'Hello';
358         $expected = $data;
359         $this->assertFormattedOutputMatches($expected, '', $data);
360     }
361
362     function testRenderTableAsString()
363     {
364         $data = new RowsOfFields([['f1' => 'A', 'f2' => 'B', 'f3' => 'C'], ['f1' => 'x', 'f2' => 'y', 'f3' => 'z']]);
365         $expected = "A\tB\tC\nx\ty\tz";
366
367         $this->assertFormattedOutputMatches($expected, 'string', $data);
368     }
369
370     function testRenderTableAsStringWithSingleField()
371     {
372         $data = new RowsOfFields([['f1' => 'q', 'f2' => 'r', 'f3' => 's'], ['f1' => 'x', 'f2' => 'y', 'f3' => 'z']]);
373         $expected = "q\nx";
374
375         $options = new FormatterOptions([FormatterOptions::DEFAULT_STRING_FIELD => 'f1']);
376
377         $this->assertFormattedOutputMatches($expected, 'string', $data, $options);
378     }
379
380     function testRenderTableAsStringWithSingleFieldAndUserSelectedField()
381     {
382         $data = new RowsOfFields([['f1' => 'q', 'f2' => 'r', 'f3' => 's'], ['f1' => 'x', 'f2' => 'y', 'f3' => 'z']]);
383         $expected = "r\ny";
384
385         $options = new FormatterOptions([FormatterOptions::DEFAULT_STRING_FIELD => 'f1']);
386
387         $this->assertFormattedOutputMatches($expected, 'string', $data, $options, ['fields' => 'f2']);
388     }
389
390     function testSimpleCsv()
391     {
392         $data = ['a', 'b', 'c'];
393         $expected = "a,b,c";
394
395         $this->assertFormattedOutputMatches($expected, 'csv', $data);
396     }
397
398     function testLinesOfCsv()
399     {
400         $data = [['a', 'b', 'c'], ['x', 'y', 'z']];
401         $expected = "a,b,c\nx,y,z";
402
403         $this->assertFormattedOutputMatches($expected, 'csv', $data);
404     }
405
406     function testCsvWithEscapedValues()
407     {
408         $data = ["Red apple", "Yellow lemon"];
409         $expected = '"Red apple","Yellow lemon"';
410
411         $this->assertFormattedOutputMatches($expected, 'csv', $data);
412     }
413
414     function testCsvWithEmbeddedSingleQuote()
415     {
416         $data = ["John's book", "Mary's laptop"];
417         $expected = <<<EOT
418 "John's book","Mary's laptop"
419 EOT;
420
421         $this->assertFormattedOutputMatches($expected, 'csv', $data);
422     }
423
424     function testCsvWithEmbeddedDoubleQuote()
425     {
426         $data = ['The "best" solution'];
427         $expected = <<<EOT
428 "The ""best"" solution"
429 EOT;
430
431         $this->assertFormattedOutputMatches($expected, 'csv', $data);
432     }
433
434     function testCsvBothKindsOfQuotes()
435     {
436         $data = ["John's \"new\" book", "Mary's \"modified\" laptop"];
437         $expected = <<<EOT
438 "John's ""new"" book","Mary's ""modified"" laptop"
439 EOT;
440
441         $this->assertFormattedOutputMatches($expected, 'csv', $data);
442     }
443
444     function testSimpleTsv()
445     {
446         $data = ['a', 'b', 'c'];
447         $expected = "a\tb\tc";
448
449         $this->assertFormattedOutputMatches($expected, 'tsv', $data);
450     }
451
452     function testLinesOfTsv()
453     {
454         $data = [['a', 'b', 'c'], ['x', 'y', 'z']];
455         $expected = "a\tb\tc\nx\ty\tz";
456
457         $this->assertFormattedOutputMatches($expected, 'tsv', $data);
458     }
459
460     function testTsvBothKindsOfQuotes()
461     {
462         $data = ["John's \"new\" book", "Mary's \"modified\" laptop"];
463         $expected = "John's \"new\" book\tMary's \"modified\" laptop";
464
465         $this->assertFormattedOutputMatches($expected, 'tsv', $data);
466     }
467
468     function testTsvWithEscapedValues()
469     {
470         $data = ["Red apple", "Yellow lemon", "Embedded\ttab"];
471         $expected = "Red apple\tYellow lemon\tEmbedded\\ttab";
472
473         $this->assertFormattedOutputMatches($expected, 'tsv', $data);
474     }
475
476     protected function missingCellTableExampleData()
477     {
478         $data = [
479             [
480                 'one' => 'a',
481                 'two' => 'b',
482                 'three' => 'c',
483             ],
484             [
485                 'one' => 'x',
486                 'three' => 'z',
487             ],
488         ];
489         return new RowsOfFields($data);
490     }
491
492     function testTableWithMissingCell()
493     {
494         $data = $this->missingCellTableExampleData();
495
496         $expected = <<<EOT
497  ----- ----- -------
498   One   Two   Three
499  ----- ----- -------
500   a     b     c
501   x           z
502  ----- ----- -------
503 EOT;
504         $this->assertFormattedOutputMatches($expected, 'table', $data);
505
506         $expectedCsv = <<<EOT
507 One,Two,Three
508 a,b,c
509 x,,z
510 EOT;
511         $this->assertFormattedOutputMatches($expectedCsv, 'csv', $data);
512
513         $expectedTsv = <<<EOT
514 a\tb\tc
515 x\t\tz
516 EOT;
517         $this->assertFormattedOutputMatches($expectedTsv, 'tsv', $data);
518
519         $expectedTsvWithHeaders = <<<EOT
520 One\tTwo\tThree
521 a\tb\tc
522 x\t\tz
523 EOT;
524         $this->assertFormattedOutputMatches($expectedTsvWithHeaders, 'tsv', $data, new FormatterOptions(), ['include-field-labels' => true]);
525     }
526
527     function testTableWithWordWrapping()
528     {
529         $options = new FormatterOptions();
530         $options->setWidth(42);
531
532         $data = [
533             [
534                 'first' => 'This is a really long cell that contains a lot of data. When it is rendered, it should be wrapped across multiple lines.',
535                 'second' => 'This is the second column of the same table. It is also very long, and should be wrapped across multiple lines, just like the first column.',
536             ]
537         ];
538         $data = new RowsOfFields($data);
539
540         $expected = <<<EOT
541  ------------------- --------------------
542   First               Second
543  ------------------- --------------------
544   This is a really    This is the second
545   long cell that      column of the same
546   contains a lot of   table. It is also
547   data. When it is    very long, and
548   rendered, it        should be wrapped
549   should be wrapped   across multiple
550   across multiple     lines, just like
551   lines.              the first column.
552  ------------------- --------------------
553 EOT;
554         $this->assertFormattedOutputMatches($expected, 'table', $data, $options);
555     }
556
557     protected function simpleTableExampleData()
558     {
559         $data = [
560             'id-123' =>
561             [
562                 'one' => 'a',
563                 'two' => 'b',
564                 'three' => 'c',
565             ],
566             'id-456' =>
567             [
568                 'one' => 'x',
569                 'two' => 'y',
570                 'three' => 'z',
571             ],
572         ];
573         return new RowsOfFields($data);
574     }
575
576     /**
577      * @expectedException \Consolidation\OutputFormatters\Exception\InvalidFormatException
578      * @expectedExceptionCode 1
579      * @expectedExceptionMessage The format table cannot be used with the data produced by this command, which was an array.  Valid formats are: csv,json,list,php,print-r,string,tsv,var_export,xml,yaml
580      */
581     function testIncompatibleDataForTableFormatter()
582     {
583         $data = $this->simpleTableExampleData()->getArrayCopy();
584         $this->assertFormattedOutputMatches('Should throw an exception before comparing the table data', 'table', $data);
585     }
586
587     /**
588      * @expectedException \Consolidation\OutputFormatters\Exception\InvalidFormatException
589      * @expectedExceptionCode 1
590      * @expectedExceptionMessage The format sections cannot be used with the data produced by this command, which was an array.  Valid formats are: csv,json,list,php,print-r,string,tsv,var_export,xml,yaml
591      */
592     function testIncompatibleDataForSectionsFormatter()
593     {
594         $data = $this->simpleTableExampleData()->getArrayCopy();
595         $this->assertFormattedOutputMatches('Should throw an exception before comparing the table data', 'sections', $data);
596     }
597
598     function testSimpleTable()
599     {
600         $data = $this->simpleTableExampleData();
601
602         $expected = <<<EOT
603  ----- ----- -------
604   One   Two   Three
605  ----- ----- -------
606   a     b     c
607   x     y     z
608  ----- ----- -------
609 EOT;
610         $this->assertFormattedOutputMatches($expected, 'table', $data);
611
612         $expectedBorderless = <<<EOT
613  ===== ===== =======
614   One   Two   Three
615  ===== ===== =======
616   a     b     c
617   x     y     z
618  ===== ===== =======
619 EOT;
620         $this->assertFormattedOutputMatches($expectedBorderless, 'table', $data, new FormatterOptions(['table-style' => 'borderless']));
621
622         $expectedJson = <<<EOT
623 {
624     "id-123": {
625         "one": "a",
626         "two": "b",
627         "three": "c"
628     },
629     "id-456": {
630         "one": "x",
631         "two": "y",
632         "three": "z"
633     }
634 }
635 EOT;
636         $this->assertFormattedOutputMatches($expectedJson, 'json', $data);
637
638         $expectedCsv = <<<EOT
639 One,Two,Three
640 a,b,c
641 x,y,z
642 EOT;
643         $this->assertFormattedOutputMatches($expectedCsv, 'csv', $data);
644
645         $expectedList = <<<EOT
646 id-123
647 id-456
648 EOT;
649         $this->assertFormattedOutputMatches($expectedList, 'list', $data);
650     }
651
652     protected function tableWithAlternativesExampleData()
653     {
654         $data = [
655             'id-123' =>
656             [
657                 'one' => 'a',
658                 'two' => ['this', 'that', 'the other thing'],
659                 'three' => 'c',
660             ],
661             'id-456' =>
662             [
663                 'one' => 'x',
664                 'two' => 'y',
665                 'three' => ['apples', 'oranges'],
666             ],
667         ];
668         return new RowsOfFieldsWithAlternatives($data);
669     }
670
671     function testTableWithAlternatives()
672     {
673         $data = $this->tableWithAlternativesExampleData();
674
675         $expected = <<<EOT
676  ----- --------------------------- ----------------
677   One   Two                         Three
678  ----- --------------------------- ----------------
679   a     this|that|the other thing   c
680   x     y                           apples|oranges
681  ----- --------------------------- ----------------
682 EOT;
683         $this->assertFormattedOutputMatches($expected, 'table', $data);
684
685         $expectedBorderless = <<<EOT
686  ===== =========================== ================
687   One   Two                         Three
688  ===== =========================== ================
689   a     this|that|the other thing   c
690   x     y                           apples|oranges
691  ===== =========================== ================
692 EOT;
693         $this->assertFormattedOutputMatches($expectedBorderless, 'table', $data, new FormatterOptions(['table-style' => 'borderless']));
694
695         $expectedJson = <<<EOT
696 {
697     "id-123": {
698         "one": "a",
699         "two": [
700             "this",
701             "that",
702             "the other thing"
703         ],
704         "three": "c"
705     },
706     "id-456": {
707         "one": "x",
708         "two": "y",
709         "three": [
710             "apples",
711             "oranges"
712         ]
713     }
714 }
715 EOT;
716         $this->assertFormattedOutputMatches($expectedJson, 'json', $data);
717
718         $expectedCsv = <<<EOT
719 One,Two,Three
720 a,"this|that|the other thing",c
721 x,y,apples|oranges
722 EOT;
723         $this->assertFormattedOutputMatches($expectedCsv, 'csv', $data);
724
725         $expectedList = <<<EOT
726 id-123
727 id-456
728 EOT;
729         $this->assertFormattedOutputMatches($expectedList, 'list', $data);
730     }
731
732     function testSimpleTableWithFieldLabels()
733     {
734         $data = $this->simpleTableExampleData();
735         $configurationData = new FormatterOptions(
736             [
737                 'field-labels' => ['one' => 'Ichi', 'two' => 'Ni', 'three' => 'San'],
738                 'row-labels' => ['id-123' => 'Walrus', 'id-456' => 'Carpenter'],
739             ]
740         );
741         $configurationDataAnnotationFormat = new FormatterOptions(
742             [
743                 'field-labels' => "one: Uno\ntwo: Dos\nthree: Tres",
744             ]
745         );
746
747         $expected = <<<EOT
748  ------ ---- -----
749   Ichi   Ni   San
750  ------ ---- -----
751   a      b    c
752   x      y    z
753  ------ ---- -----
754 EOT;
755         $this->assertFormattedOutputMatches($expected, 'table', $data, $configurationData);
756
757         $expectedSidewaysTable = <<<EOT
758  ------ --- ---
759   Ichi   a   x
760   Ni     b   y
761   San    c   z
762  ------ --- ---
763 EOT;
764         $this->assertFormattedOutputMatches($expectedSidewaysTable, 'table', $data, $configurationData->override(['list-orientation' => true]));
765
766         $expectedAnnotationFormatConfigData = <<<EOT
767  ----- ----- ------
768   Uno   Dos   Tres
769  ----- ----- ------
770   a     b     c
771   x     y     z
772  ----- ----- ------
773 EOT;
774         $this->assertFormattedOutputMatches($expectedAnnotationFormatConfigData, 'table', $data, $configurationDataAnnotationFormat);
775
776         $expectedWithNoFields = <<<EOT
777  --- --- ---
778   a   b   c
779   x   y   z
780  --- --- ---
781 EOT;
782         $this->assertFormattedOutputMatches($expectedWithNoFields, 'table', $data, $configurationData, ['include-field-labels' => false]);
783
784         $expectedWithReorderedFields = <<<EOT
785  ----- ------
786   San   Ichi
787  ----- ------
788   c     a
789   z     x
790  ----- ------
791 EOT;
792         $this->assertFormattedOutputMatches($expectedWithReorderedFields, 'table', $data, $configurationData, ['fields' => ['three', 'one']]);
793         $this->assertFormattedOutputMatches($expectedWithReorderedFields, 'table', $data, $configurationData, ['fields' => ['San', 'Ichi']]);
794         $this->assertFormattedOutputMatches($expectedWithReorderedFields, 'table', $data, $configurationData, ['fields' => 'San,Ichi']);
795
796         $expectedWithRegexField = <<<EOT
797  ------ -----
798   Ichi   San
799  ------ -----
800   a      c
801   x      z
802  ------ -----
803 EOT;
804         $this->assertFormattedOutputMatches($expectedWithRegexField, 'table', $data, $configurationData, ['fields' => ['/e$/']]);
805         $this->assertFormattedOutputMatches($expectedWithRegexField, 'table', $data, $configurationData, ['fields' => ['*e']]);
806         $this->assertFormattedOutputMatches($expectedWithRegexField, 'table', $data, $configurationData, ['default-fields' => ['*e']]);
807
808         $expectedSections = <<<EOT
809
810 Walrus
811  One   a
812  Two   b
813  Three c
814
815 Carpenter
816  One   x
817  Two   y
818  Three z
819 EOT;
820         $this->assertFormattedOutputMatches($expectedSections, 'sections', $data, $configurationData);
821
822         $expectedJson = <<<EOT
823 {
824     "id-123": {
825         "three": "c",
826         "one": "a"
827     },
828     "id-456": {
829         "three": "z",
830         "one": "x"
831     }
832 }
833 EOT;
834         $this->assertFormattedOutputMatches($expectedJson, 'json', $data, $configurationData, ['fields' => ['San', 'Ichi']]);
835
836         $expectedSingleField = <<<EOT
837  -----
838   San
839  -----
840   c
841   z
842  -----
843 EOT;
844         $this->assertFormattedOutputMatches($expectedSingleField, 'table', $data, $configurationData, ['field' => 'San']);
845
846         $expectedEmptyColumn = <<<EOT
847  -----
848   San
849  -----
850 EOT;
851
852         $this->assertFormattedOutputMatches($expectedEmptyColumn, 'table', new RowsOfFields([]), $configurationData, ['field' => 'San']);
853
854         $this->assertFormattedOutputMatches('', '', new RowsOfFields([]), $configurationData, ['field' => 'San']);
855         $this->assertFormattedOutputMatches('[]', 'json', new RowsOfFields([]), $configurationData, ['field' => 'San']);
856     }
857
858     /**
859      * @expectedException \Consolidation\OutputFormatters\Exception\UnknownFieldException
860      * @expectedExceptionCode 1
861      * @expectedExceptionMessage The requested field, 'Shi', is not defined.
862      */
863     function testNoSuchFieldException()
864     {
865         $configurationData = new FormatterOptions(
866             [
867                 'field-labels' => ['one' => 'Ichi', 'two' => 'Ni', 'three' => 'San'],
868                 'row-labels' => ['id-123' => 'Walrus', 'id-456' => 'Carpenter'],
869             ]
870         );
871         $data = $this->simpleTableExampleData();
872         $this->assertFormattedOutputMatches('Will throw before comparing', 'table', $data, $configurationData, ['field' => 'Shi']);
873     }
874
875     protected function simpleListExampleData()
876     {
877         $data = [
878             'one' => 'apple',
879             'two' => 'banana',
880             'three' => 'carrot',
881         ];
882         return new PropertyList($data);
883     }
884
885     // Test with the deprecated data structure
886     protected function simpleListExampleDataUsingAssociativeList()
887     {
888         $data = [
889             'one' => 'apple',
890             'two' => 'banana',
891             'three' => 'carrot',
892         ];
893         return new AssociativeList($data);
894     }
895
896     /**
897      * @expectedException \Consolidation\OutputFormatters\Exception\InvalidFormatException
898      * @expectedExceptionCode 1
899      * @expectedExceptionMessage The format table cannot be used with the data produced by this command, which was an array.  Valid formats are: csv,json,list,php,print-r,string,tsv,var_export,xml,yaml
900      */
901     function testIncompatibleListDataForTableFormatter()
902     {
903         $data = $this->simpleListExampleData();
904         $this->assertFormattedOutputMatches('Should throw an exception before comparing the table data', 'table', $data->getArrayCopy());
905     }
906
907     function testEmptyList()
908     {
909         $data = new RowsOfFields([]);
910
911         $expected = <<<EOT
912  --- ---- -----
913   I   II   III
914  --- ---- -----
915 EOT;
916
917         // If we provide field labels, then the output will change to reflect that.
918         $formatterOptionsWithFieldLables = new FormatterOptions();
919         $formatterOptionsWithFieldLables
920             ->setFieldLabels(['one' => 'I', 'two' => 'II', 'three' => 'III']);
921         $this->assertFormattedOutputMatches($expected, 'table', $data, $formatterOptionsWithFieldLables);
922     }
923
924     function testSimpleList()
925     {
926
927         $expected = <<<EOT
928  ------- --------
929   One     apple
930   Two     banana
931   Three   carrot
932  ------- --------
933 EOT;
934         $data = $this->simpleListExampleDataUsingAssociativeList();
935
936         $this->assertFormattedOutputMatches($expected, 'table', $data);
937
938         $data = $this->simpleListExampleData();
939
940         $this->assertFormattedOutputMatches($expected, 'table', $data);
941
942         $expected = <<<EOT
943  ----- --------
944   I     apple
945   II    banana
946   III   carrot
947  ----- --------
948 EOT;
949         // If we provide field labels, then the output will change to reflect that.
950         $formatterOptionsWithFieldLables = new FormatterOptions();
951         $formatterOptionsWithFieldLables
952             ->setFieldLabels(['one' => 'I', 'two' => 'II', 'three' => 'III']);
953         $this->assertFormattedOutputMatches($expected, 'table', $data, $formatterOptionsWithFieldLables);
954
955         $expectedDrushStyleTable = <<<EOT
956  One   : apple
957  Two   : banana
958  Three : carrot
959 EOT;
960
961         // If we provide field labels, then the output will change to reflect that.
962         $formatterOptionsWithFieldLables = new FormatterOptions();
963         $formatterOptionsWithFieldLables
964             ->setTableStyle('compact')
965             ->setListDelimiter(':');
966         $this->assertFormattedOutputMatches($expectedDrushStyleTable, 'table', $data, $formatterOptionsWithFieldLables);
967
968
969         // Adding an extra field that does not exist in the data set should not change the output
970         $formatterOptionsWithExtraFieldLables = new FormatterOptions();
971         $formatterOptionsWithExtraFieldLables
972             ->setFieldLabels(['one' => 'I', 'two' => 'II', 'three' => 'III', 'four' => 'IV']);
973         $this->assertFormattedOutputMatches($expected, 'table', $data, $formatterOptionsWithExtraFieldLables);
974
975         $expectedRotated = <<<EOT
976  ------- -------- --------
977   One     Two      Three
978  ------- -------- --------
979   apple   banana   carrot
980  ------- -------- --------
981 EOT;
982         $this->assertFormattedOutputMatches($expectedRotated, 'table', $data, new FormatterOptions(['list-orientation' => false]));
983
984         $expectedList = <<< EOT
985 apple
986 banana
987 carrot
988 EOT;
989         $this->assertFormattedOutputMatches($expectedList, 'list', $data);
990
991         $expectedReorderedList = <<< EOT
992 carrot
993 apple
994 EOT;
995         $options = new FormatterOptions([FormatterOptions::FIELDS => 'three,one']);
996         $this->assertFormattedOutputMatches($expectedReorderedList, 'list', $data, $options);
997
998         $expectedCsv = <<< EOT
999 One,Two,Three
1000 apple,banana,carrot
1001 EOT;
1002         $this->assertFormattedOutputMatches($expectedCsv, 'csv', $data);
1003
1004         $expectedCsvNoHeaders = 'apple,banana,carrot';
1005         $this->assertFormattedOutputMatches($expectedCsvNoHeaders, 'csv', $data, new FormatterOptions(), ['include-field-labels' => false]);
1006
1007         // Next, configure the formatter options with 'include-field-labels',
1008         // but set --include-field-labels to turn the option back on again.
1009         $options = new FormatterOptions(['include-field-labels' => false]);
1010         $input = new StringInput('test --include-field-labels');
1011         $optionDefinitions = [
1012             new InputArgument('unused', InputArgument::REQUIRED),
1013             new InputOption('include-field-labels', null, InputOption::VALUE_NONE),
1014         ];
1015         $definition = new InputDefinition($optionDefinitions);
1016         $input->bind($definition);
1017         $testValue = $input->getOption('include-field-labels');
1018         $this->assertTrue($testValue);
1019         $hasFieldLabels = $input->hasOption('include-field-labels');
1020         $this->assertTrue($hasFieldLabels);
1021
1022         $this->assertFormattedOutputMatches($expectedCsvNoHeaders, 'csv', $data, $options);
1023         $options->setInput($input);
1024         $this->assertFormattedOutputMatches($expectedCsv, 'csv', $data, $options);
1025     }
1026
1027     protected function associativeListWithRenderer()
1028     {
1029         $data = [
1030             'one' => 'apple',
1031             'two' => ['banana', 'plantain'],
1032             'three' => 'carrot',
1033             'four' => ['peaches', 'pumpkin pie'],
1034         ];
1035         $list = new PropertyList($data);
1036
1037         $list->addRendererFunction(
1038             function ($key, $cellData, FormatterOptions $options)
1039             {
1040                 if (is_array($cellData)) {
1041                     return implode(',', $cellData);
1042                 }
1043                 return $cellData;
1044             }
1045         );
1046
1047         return $list;
1048     }
1049
1050     protected function associativeListWithCsvCells()
1051     {
1052         $data = [
1053             'one' => 'apple',
1054             'two' => ['banana', 'plantain'],
1055             'three' => 'carrot',
1056             'four' => ['peaches', 'pumpkin pie'],
1057         ];
1058         return new PropertyListWithCsvCells($data);
1059     }
1060
1061     function testPropertyListWithCsvCells()
1062     {
1063         $this->doPropertyListWithCsvCells($this->associativeListWithRenderer());
1064         $this->doPropertyListWithCsvCells($this->associativeListWithCsvCells());
1065     }
1066
1067     function doPropertyListWithCsvCells($data)
1068     {
1069         $expected = <<<EOT
1070  ------- ---------------------
1071   One     apple
1072   Two     banana,plantain
1073   Three   carrot
1074   Four    peaches,pumpkin pie
1075  ------- ---------------------
1076 EOT;
1077         $this->assertFormattedOutputMatches($expected, 'table', $data);
1078
1079         $expectedList = <<< EOT
1080 apple
1081 banana,plantain
1082 carrot
1083 peaches,pumpkin pie
1084 EOT;
1085         $this->assertFormattedOutputMatches($expectedList, 'list', $data);
1086
1087         $expectedCsv = <<< EOT
1088 One,Two,Three,Four
1089 apple,"banana,plantain",carrot,"peaches,pumpkin pie"
1090 EOT;
1091         $this->assertFormattedOutputMatches($expectedCsv, 'csv', $data);
1092
1093         $expectedCsvNoHeaders = 'apple,"banana,plantain",carrot,"peaches,pumpkin pie"';
1094         $this->assertFormattedOutputMatches($expectedCsvNoHeaders, 'csv', $data, new FormatterOptions(), ['include-field-labels' => false]);
1095
1096         $expectedTsv = <<< EOT
1097 apple\tbanana,plantain\tcarrot\tpeaches,pumpkin pie
1098 EOT;
1099         $this->assertFormattedOutputMatches($expectedTsv, 'tsv', $data);
1100
1101     }
1102
1103     function testSimpleListWithFieldLabels()
1104     {
1105         $data = $this->simpleListExampleData();
1106         $configurationData = new FormatterOptions(
1107             [
1108                 'field-labels' => ['one' => 'Ichi', 'two' => 'Ni', 'three' => 'San'],
1109             ]
1110         );
1111
1112         $expected = <<<EOT
1113  ------ --------
1114   Ichi   apple
1115   Ni     banana
1116   San    carrot
1117  ------ --------
1118 EOT;
1119         $this->assertFormattedOutputMatches($expected, 'table', $data, $configurationData);
1120
1121         $expectedWithReorderedFields = <<<EOT
1122  ------ --------
1123   San    carrot
1124   Ichi   apple
1125  ------ --------
1126 EOT;
1127         $this->assertFormattedOutputMatches($expectedWithReorderedFields, 'table', $data, $configurationData, ['fields' => ['three', 'one']]);
1128         $this->assertFormattedOutputMatches($expectedWithReorderedFields, 'table', $data, $configurationData, ['fields' => ['San', 'Ichi']]);
1129
1130         $expectedJson = <<<EOT
1131 {
1132     "three": "carrot",
1133     "one": "apple"
1134 }
1135 EOT;
1136         $this->assertFormattedOutputMatches($expectedJson, 'json', $data, $configurationData, ['fields' => ['San', 'Ichi']]);
1137     }
1138
1139     function testSimpleXml()
1140     {
1141         $data = [
1142             'name' => 'primary',
1143             'description' => 'The primary colors of the color wheel.',
1144             'colors' =>
1145             [
1146                 'red',
1147                 'yellow',
1148                 'blue',
1149             ],
1150         ];
1151
1152         $expected = <<<EOT
1153 <?xml version="1.0" encoding="UTF-8"?>
1154 <document name="primary">
1155   <description>The primary colors of the color wheel.</description>
1156   <colors>
1157     <color>red</color>
1158     <color>yellow</color>
1159     <color>blue</color>
1160   </colors>
1161 </document>
1162 EOT;
1163
1164         $this->assertFormattedOutputMatches($expected, 'xml', $data);
1165     }
1166
1167     function domDocumentData()
1168     {
1169         $dom = new \DOMDocument('1.0', 'UTF-8');
1170
1171         $document = $dom->createElement('document');
1172         $dom->appendChild($document);
1173
1174         $document->setAttribute('name', 'primary');
1175         $description = $dom->createElement('description');
1176         $document->appendChild($description);
1177         $description->appendChild($dom->createTextNode('The primary colors of the color wheel.'));
1178
1179         $this->domCreateElements($dom, $document, 'color', ['red', 'yellow', 'blue']);
1180
1181         return $dom;
1182     }
1183
1184     function domCreateElements($dom, $element, $name, $data)
1185     {
1186         $container = $dom->createElement("{$name}s");
1187         $element->appendChild($container);
1188         foreach ($data as $value) {
1189             $child = $dom->createElement($name);
1190             $container->appendChild($child);
1191             $child->appendChild($dom->createTextNode($value));
1192         }
1193     }
1194
1195     function complexDomDocumentData()
1196     {
1197         $dom = new \DOMDocument('1.0', 'UTF-8');
1198
1199         $document = $dom->createElement('document');
1200         $dom->appendChild($document);
1201
1202         $document->setAttribute('name', 'widget-collection');
1203         $description = $dom->createElement('description');
1204         $document->appendChild($description);
1205         $description->appendChild($dom->createTextNode('A couple of widgets.'));
1206
1207         $widgets = $dom->createElement('widgets');
1208         $document->appendChild($widgets);
1209
1210         $widget = $dom->createElement('widget');
1211         $widgets->appendChild($widget);
1212         $widget->setAttribute('name', 'usual');
1213         $this->domCreateElements($dom, $widget, 'color', ['red', 'yellow', 'blue']);
1214         $this->domCreateElements($dom, $widget, 'shape', ['square', 'circle', 'triangle']);
1215
1216         $widget = $dom->createElement('widget');
1217         $widgets->appendChild($widget);
1218         $widget->setAttribute('name', 'unusual');
1219         $this->domCreateElements($dom, $widget, 'color', ['muave', 'puce', 'umber']);
1220         $this->domCreateElements($dom, $widget, 'shape', ['elipse', 'rhombus', 'trapazoid']);
1221
1222         return $dom;
1223     }
1224
1225     function domDocumentTestValues()
1226     {
1227
1228         $expectedXml = <<<EOT
1229 <?xml version="1.0" encoding="UTF-8"?>
1230 <document name="primary">
1231   <description>The primary colors of the color wheel.</description>
1232   <colors>
1233     <color>red</color>
1234     <color>yellow</color>
1235     <color>blue</color>
1236   </colors>
1237 </document>
1238 EOT;
1239
1240         $expectedJson = <<<EOT
1241 {
1242     "name": "primary",
1243     "description": "The primary colors of the color wheel.",
1244     "colors": [
1245         "red",
1246         "yellow",
1247         "blue"
1248     ]
1249 }
1250 EOT;
1251
1252         $expectedComplexXml = <<<EOT
1253 <?xml version="1.0" encoding="UTF-8"?>
1254 <document name="widget-collection">
1255   <description>A couple of widgets.</description>
1256   <widgets>
1257     <widget name="usual">
1258       <colors>
1259         <color>red</color>
1260         <color>yellow</color>
1261         <color>blue</color>
1262       </colors>
1263       <shapes>
1264         <shape>square</shape>
1265         <shape>circle</shape>
1266         <shape>triangle</shape>
1267       </shapes>
1268     </widget>
1269     <widget name="unusual">
1270       <colors>
1271         <color>muave</color>
1272         <color>puce</color>
1273         <color>umber</color>
1274       </colors>
1275       <shapes>
1276         <shape>elipse</shape>
1277         <shape>rhombus</shape>
1278         <shape>trapazoid</shape>
1279       </shapes>
1280     </widget>
1281   </widgets>
1282 </document>
1283 EOT;
1284
1285         $expectedComplexJson = <<<EOT
1286 {
1287     "name": "widget-collection",
1288     "description": "A couple of widgets.",
1289     "widgets": {
1290         "usual": {
1291             "name": "usual",
1292             "colors": [
1293                 "red",
1294                 "yellow",
1295                 "blue"
1296             ],
1297             "shapes": [
1298                 "square",
1299                 "circle",
1300                 "triangle"
1301             ]
1302         },
1303         "unusual": {
1304             "name": "unusual",
1305             "colors": [
1306                 "muave",
1307                 "puce",
1308                 "umber"
1309             ],
1310             "shapes": [
1311                 "elipse",
1312                 "rhombus",
1313                 "trapazoid"
1314             ]
1315         }
1316     }
1317 }
1318 EOT;
1319
1320         return [
1321             [
1322                 $this->domDocumentData(),
1323                 $expectedXml,
1324                 $expectedJson,
1325             ],
1326             [
1327                 $this->complexDomDocumentData(),
1328                 $expectedComplexXml,
1329                 $expectedComplexJson,
1330             ],
1331         ];
1332     }
1333
1334     /**
1335      *  @dataProvider domDocumentTestValues
1336      */
1337     function testDomData($data, $expectedXml, $expectedJson)
1338     {
1339         $this->assertFormattedOutputMatches($expectedXml, 'xml', $data);
1340         $this->assertFormattedOutputMatches($expectedJson, 'json', $data);
1341
1342         // Check to see if we get the same xml data if we convert from
1343         // DOM -> array -> DOM.
1344         $expectedJsonAsArray = (array)json_decode($expectedJson);
1345         $this->assertFormattedOutputMatches($expectedXml, 'xml', $expectedJsonAsArray);
1346     }
1347
1348     /**
1349      * @expectedException \Exception
1350      * @expectedExceptionCode 1
1351      * @expectedExceptionMessage Data provided to Consolidation\OutputFormatters\Formatters\XmlFormatter must be either an instance of DOMDocument or an array. Instead, a string was provided.
1352      */
1353     function testDataTypeForXmlFormatter()
1354     {
1355         $this->assertFormattedOutputMatches('Will fail, not return', 'xml', 'Strings cannot be converted to XML');
1356     }
1357 }