Version 1
[yaffs-website] / vendor / consolidation / output-formatters / tests / testFormatters.php
diff --git a/vendor/consolidation/output-formatters/tests/testFormatters.php b/vendor/consolidation/output-formatters/tests/testFormatters.php
new file mode 100644 (file)
index 0000000..18b5767
--- /dev/null
@@ -0,0 +1,1357 @@
+<?php
+namespace Consolidation\OutputFormatters;
+
+use Consolidation\TestUtils\PropertyListWithCsvCells;
+use Consolidation\TestUtils\RowsOfFieldsWithAlternatives;
+use Consolidation\OutputFormatters\Options\FormatterOptions;
+use Consolidation\OutputFormatters\StructuredData\AssociativeList;
+use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
+use Consolidation\OutputFormatters\StructuredData\PropertyList;
+use Symfony\Component\Console\Output\BufferedOutput;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\StringInput;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputDefinition;
+
+class FormattersTests extends \PHPUnit_Framework_TestCase
+{
+    protected $formatterManager;
+
+    function setup() {
+        $this->formatterManager = new FormatterManager();
+    }
+
+    function assertFormattedOutputMatches($expected, $format, $data, FormatterOptions $options = null, $userOptions = []) {
+        if (!$options) {
+            $options = new FormatterOptions();
+        }
+        $options->setOptions($userOptions);
+        $output = new BufferedOutput();
+        $this->formatterManager->write($output, $format, $data, $options);
+        $actual = preg_replace('#[ \t]*$#sm', '', $output->fetch());
+        $this->assertEquals(rtrim($expected), rtrim($actual));
+    }
+
+    function testSimpleYaml()
+    {
+        $data = [
+            'one' => 'a',
+            'two' => 'b',
+            'three' => 'c',
+        ];
+
+        $expected = <<<EOT
+one: a
+two: b
+three: c
+EOT;
+
+        $this->assertFormattedOutputMatches($expected, 'yaml', $data);
+    }
+
+    function testNestedYaml()
+    {
+        $data = [
+            'one' => [
+                'i' => ['a', 'b', 'c'],
+            ],
+            'two' => [
+                'ii' => ['q', 'r', 's'],
+            ],
+            'three' => [
+                'iii' => ['t', 'u', 'v'],
+            ],
+        ];
+
+        $expected = <<<EOT
+one:
+  i:
+    - a
+    - b
+    - c
+two:
+  ii:
+    - q
+    - r
+    - s
+three:
+  iii:
+    - t
+    - u
+    - v
+EOT;
+
+        $this->assertFormattedOutputMatches($expected, 'yaml', $data);
+    }
+
+    function testSimpleJson()
+    {
+        $data = [
+            'one' => 'a',
+            'two' => 'b',
+            'three' => 'c',
+        ];
+
+        $expected = <<<EOT
+{
+    "one": "a",
+    "two": "b",
+    "three": "c"
+}
+EOT;
+
+        $this->assertFormattedOutputMatches($expected, 'json', $data);
+    }
+
+    function testSerializeFormat()
+    {
+        $data = [
+            'one' => 'a',
+            'two' => 'b',
+            'three' => 'c',
+        ];
+
+        $expected = 'a:3:{s:3:"one";s:1:"a";s:3:"two";s:1:"b";s:5:"three";s:1:"c";}';
+
+        $this->assertFormattedOutputMatches($expected, 'php', $data);
+    }
+
+    function testNestedJson()
+    {
+        $data = [
+            'one' => [
+                'i' => ['a', 'b', 'c'],
+            ],
+            'two' => [
+                'ii' => ['q', 'r', 's'],
+            ],
+            'three' => [
+                'iii' => ['t', 'u', 'v'],
+            ],
+        ];
+
+        $expected = <<<EOT
+{
+    "one": {
+        "i": [
+            "a",
+            "b",
+            "c"
+        ]
+    },
+    "two": {
+        "ii": [
+            "q",
+            "r",
+            "s"
+        ]
+    },
+    "three": {
+        "iii": [
+            "t",
+            "u",
+            "v"
+        ]
+    }
+}
+EOT;
+
+        $this->assertFormattedOutputMatches($expected, 'json', $data);
+    }
+
+    function testSimplePrintR()
+    {
+        $data = [
+            'one' => 'a',
+            'two' => 'b',
+            'three' => 'c',
+        ];
+
+        $expected = <<<EOT
+Array
+(
+    [one] => a
+    [two] => b
+    [three] => c
+)
+EOT;
+
+        $this->assertFormattedOutputMatches($expected, 'print-r', $data);
+    }
+
+    function testNestedPrintR()
+    {
+        $data = [
+            'one' => [
+                'i' => ['a', 'b', 'c'],
+            ],
+            'two' => [
+                'ii' => ['q', 'r', 's'],
+            ],
+            'three' => [
+                'iii' => ['t', 'u', 'v'],
+            ],
+        ];
+
+        $expected = <<<EOT
+Array
+(
+    [one] => Array
+        (
+            [i] => Array
+                (
+                    [0] => a
+                    [1] => b
+                    [2] => c
+                )
+
+        )
+
+    [two] => Array
+        (
+            [ii] => Array
+                (
+                    [0] => q
+                    [1] => r
+                    [2] => s
+                )
+
+        )
+
+    [three] => Array
+        (
+            [iii] => Array
+                (
+                    [0] => t
+                    [1] => u
+                    [2] => v
+                )
+
+        )
+
+)
+EOT;
+
+        $this->assertFormattedOutputMatches($expected, 'print-r', $data);
+    }
+
+    function testSimpleVarExport()
+    {
+        $data = [
+            'one' => 'a',
+            'two' => 'b',
+            'three' => 'c',
+        ];
+
+        $expected = <<<EOT
+array (
+  'one' => 'a',
+  'two' => 'b',
+  'three' => 'c',
+)
+EOT;
+
+        $this->assertFormattedOutputMatches($expected, 'var_export', $data);
+    }
+
+    function testNestedVarExport()
+    {
+        $data = [
+            'one' => [
+                'i' => ['a', 'b', 'c'],
+            ],
+            'two' => [
+                'ii' => ['q', 'r', 's'],
+            ],
+            'three' => [
+                'iii' => ['t', 'u', 'v'],
+            ],
+        ];
+
+        $expected = <<<EOT
+array (
+  'one' =>
+  array (
+    'i' =>
+    array (
+      0 => 'a',
+      1 => 'b',
+      2 => 'c',
+    ),
+  ),
+  'two' =>
+  array (
+    'ii' =>
+    array (
+      0 => 'q',
+      1 => 'r',
+      2 => 's',
+    ),
+  ),
+  'three' =>
+  array (
+    'iii' =>
+    array (
+      0 => 't',
+      1 => 'u',
+      2 => 'v',
+    ),
+  ),
+)
+EOT;
+
+        $this->assertFormattedOutputMatches($expected, 'var_export', $data);
+    }
+
+    function testList()
+    {
+        $data = [
+            'one' => 'a',
+            'two' => 'b',
+            'three' => 'c',
+        ];
+
+        $expected = <<<EOT
+a
+b
+c
+EOT;
+
+        $this->assertFormattedOutputMatches($expected, 'list', $data);
+    }
+
+    /**
+     * @expectedException \Consolidation\OutputFormatters\Exception\UnknownFormatException
+     * @expectedExceptionCode 1
+     * @expectedExceptionMessage The requested format, 'no-such-format', is not available.
+     */
+    function testBadFormat()
+    {
+        $this->assertFormattedOutputMatches('Will fail, not return', 'no-such-format', ['a' => 'b']);
+    }
+
+    /**
+     * @expectedException \Consolidation\OutputFormatters\Exception\IncompatibleDataException
+     * @expectedExceptionCode 1
+     * @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.
+     */
+    function testBadDataTypeForCsv()
+    {
+        $this->assertFormattedOutputMatches('Will fail, not return', 'csv', 'String cannot be converted to csv');
+    }
+
+    /**
+     * @expectedException \Consolidation\OutputFormatters\Exception\IncompatibleDataException
+     * @expectedExceptionCode 1
+     * @expectedExceptionMessage Data provided to Consolidation\OutputFormatters\Formatters\JsonFormatter must be an array. Instead, a string was provided.
+     */
+    function testBadDataTypeForJson()
+    {
+        $this->assertFormattedOutputMatches('Will fail, not return', 'json', 'String cannot be converted to json');
+    }
+
+    function testNoFormatterSelected()
+    {
+        $data = 'Hello';
+        $expected = $data;
+        $this->assertFormattedOutputMatches($expected, '', $data);
+    }
+
+    function testRenderTableAsString()
+    {
+        $data = new RowsOfFields([['f1' => 'A', 'f2' => 'B', 'f3' => 'C'], ['f1' => 'x', 'f2' => 'y', 'f3' => 'z']]);
+        $expected = "A\tB\tC\nx\ty\tz";
+
+        $this->assertFormattedOutputMatches($expected, 'string', $data);
+    }
+
+    function testRenderTableAsStringWithSingleField()
+    {
+        $data = new RowsOfFields([['f1' => 'q', 'f2' => 'r', 'f3' => 's'], ['f1' => 'x', 'f2' => 'y', 'f3' => 'z']]);
+        $expected = "q\nx";
+
+        $options = new FormatterOptions([FormatterOptions::DEFAULT_STRING_FIELD => 'f1']);
+
+        $this->assertFormattedOutputMatches($expected, 'string', $data, $options);
+    }
+
+    function testRenderTableAsStringWithSingleFieldAndUserSelectedField()
+    {
+        $data = new RowsOfFields([['f1' => 'q', 'f2' => 'r', 'f3' => 's'], ['f1' => 'x', 'f2' => 'y', 'f3' => 'z']]);
+        $expected = "r\ny";
+
+        $options = new FormatterOptions([FormatterOptions::DEFAULT_STRING_FIELD => 'f1']);
+
+        $this->assertFormattedOutputMatches($expected, 'string', $data, $options, ['fields' => 'f2']);
+    }
+
+    function testSimpleCsv()
+    {
+        $data = ['a', 'b', 'c'];
+        $expected = "a,b,c";
+
+        $this->assertFormattedOutputMatches($expected, 'csv', $data);
+    }
+
+    function testLinesOfCsv()
+    {
+        $data = [['a', 'b', 'c'], ['x', 'y', 'z']];
+        $expected = "a,b,c\nx,y,z";
+
+        $this->assertFormattedOutputMatches($expected, 'csv', $data);
+    }
+
+    function testCsvWithEscapedValues()
+    {
+        $data = ["Red apple", "Yellow lemon"];
+        $expected = '"Red apple","Yellow lemon"';
+
+        $this->assertFormattedOutputMatches($expected, 'csv', $data);
+    }
+
+    function testCsvWithEmbeddedSingleQuote()
+    {
+        $data = ["John's book", "Mary's laptop"];
+        $expected = <<<EOT
+"John's book","Mary's laptop"
+EOT;
+
+        $this->assertFormattedOutputMatches($expected, 'csv', $data);
+    }
+
+    function testCsvWithEmbeddedDoubleQuote()
+    {
+        $data = ['The "best" solution'];
+        $expected = <<<EOT
+"The ""best"" solution"
+EOT;
+
+        $this->assertFormattedOutputMatches($expected, 'csv', $data);
+    }
+
+    function testCsvBothKindsOfQuotes()
+    {
+        $data = ["John's \"new\" book", "Mary's \"modified\" laptop"];
+        $expected = <<<EOT
+"John's ""new"" book","Mary's ""modified"" laptop"
+EOT;
+
+        $this->assertFormattedOutputMatches($expected, 'csv', $data);
+    }
+
+    function testSimpleTsv()
+    {
+        $data = ['a', 'b', 'c'];
+        $expected = "a\tb\tc";
+
+        $this->assertFormattedOutputMatches($expected, 'tsv', $data);
+    }
+
+    function testLinesOfTsv()
+    {
+        $data = [['a', 'b', 'c'], ['x', 'y', 'z']];
+        $expected = "a\tb\tc\nx\ty\tz";
+
+        $this->assertFormattedOutputMatches($expected, 'tsv', $data);
+    }
+
+    function testTsvBothKindsOfQuotes()
+    {
+        $data = ["John's \"new\" book", "Mary's \"modified\" laptop"];
+        $expected = "John's \"new\" book\tMary's \"modified\" laptop";
+
+        $this->assertFormattedOutputMatches($expected, 'tsv', $data);
+    }
+
+    function testTsvWithEscapedValues()
+    {
+        $data = ["Red apple", "Yellow lemon", "Embedded\ttab"];
+        $expected = "Red apple\tYellow lemon\tEmbedded\\ttab";
+
+        $this->assertFormattedOutputMatches($expected, 'tsv', $data);
+    }
+
+    protected function missingCellTableExampleData()
+    {
+        $data = [
+            [
+                'one' => 'a',
+                'two' => 'b',
+                'three' => 'c',
+            ],
+            [
+                'one' => 'x',
+                'three' => 'z',
+            ],
+        ];
+        return new RowsOfFields($data);
+    }
+
+    function testTableWithMissingCell()
+    {
+        $data = $this->missingCellTableExampleData();
+
+        $expected = <<<EOT
+ ----- ----- -------
+  One   Two   Three
+ ----- ----- -------
+  a     b     c
+  x           z
+ ----- ----- -------
+EOT;
+        $this->assertFormattedOutputMatches($expected, 'table', $data);
+
+        $expectedCsv = <<<EOT
+One,Two,Three
+a,b,c
+x,,z
+EOT;
+        $this->assertFormattedOutputMatches($expectedCsv, 'csv', $data);
+
+        $expectedTsv = <<<EOT
+a\tb\tc
+x\t\tz
+EOT;
+        $this->assertFormattedOutputMatches($expectedTsv, 'tsv', $data);
+
+        $expectedTsvWithHeaders = <<<EOT
+One\tTwo\tThree
+a\tb\tc
+x\t\tz
+EOT;
+        $this->assertFormattedOutputMatches($expectedTsvWithHeaders, 'tsv', $data, new FormatterOptions(), ['include-field-labels' => true]);
+    }
+
+    function testTableWithWordWrapping()
+    {
+        $options = new FormatterOptions();
+        $options->setWidth(42);
+
+        $data = [
+            [
+                'first' => 'This is a really long cell that contains a lot of data. When it is rendered, it should be wrapped across multiple lines.',
+                '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.',
+            ]
+        ];
+        $data = new RowsOfFields($data);
+
+        $expected = <<<EOT
+ ------------------- --------------------
+  First               Second
+ ------------------- --------------------
+  This is a really    This is the second
+  long cell that      column of the same
+  contains a lot of   table. It is also
+  data. When it is    very long, and
+  rendered, it        should be wrapped
+  should be wrapped   across multiple
+  across multiple     lines, just like
+  lines.              the first column.
+ ------------------- --------------------
+EOT;
+        $this->assertFormattedOutputMatches($expected, 'table', $data, $options);
+    }
+
+    protected function simpleTableExampleData()
+    {
+        $data = [
+            'id-123' =>
+            [
+                'one' => 'a',
+                'two' => 'b',
+                'three' => 'c',
+            ],
+            'id-456' =>
+            [
+                'one' => 'x',
+                'two' => 'y',
+                'three' => 'z',
+            ],
+        ];
+        return new RowsOfFields($data);
+    }
+
+    /**
+     * @expectedException \Consolidation\OutputFormatters\Exception\InvalidFormatException
+     * @expectedExceptionCode 1
+     * @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
+     */
+    function testIncompatibleDataForTableFormatter()
+    {
+        $data = $this->simpleTableExampleData()->getArrayCopy();
+        $this->assertFormattedOutputMatches('Should throw an exception before comparing the table data', 'table', $data);
+    }
+
+    /**
+     * @expectedException \Consolidation\OutputFormatters\Exception\InvalidFormatException
+     * @expectedExceptionCode 1
+     * @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
+     */
+    function testIncompatibleDataForSectionsFormatter()
+    {
+        $data = $this->simpleTableExampleData()->getArrayCopy();
+        $this->assertFormattedOutputMatches('Should throw an exception before comparing the table data', 'sections', $data);
+    }
+
+    function testSimpleTable()
+    {
+        $data = $this->simpleTableExampleData();
+
+        $expected = <<<EOT
+ ----- ----- -------
+  One   Two   Three
+ ----- ----- -------
+  a     b     c
+  x     y     z
+ ----- ----- -------
+EOT;
+        $this->assertFormattedOutputMatches($expected, 'table', $data);
+
+        $expectedBorderless = <<<EOT
+ ===== ===== =======
+  One   Two   Three
+ ===== ===== =======
+  a     b     c
+  x     y     z
+ ===== ===== =======
+EOT;
+        $this->assertFormattedOutputMatches($expectedBorderless, 'table', $data, new FormatterOptions(['table-style' => 'borderless']));
+
+        $expectedJson = <<<EOT
+{
+    "id-123": {
+        "one": "a",
+        "two": "b",
+        "three": "c"
+    },
+    "id-456": {
+        "one": "x",
+        "two": "y",
+        "three": "z"
+    }
+}
+EOT;
+        $this->assertFormattedOutputMatches($expectedJson, 'json', $data);
+
+        $expectedCsv = <<<EOT
+One,Two,Three
+a,b,c
+x,y,z
+EOT;
+        $this->assertFormattedOutputMatches($expectedCsv, 'csv', $data);
+
+        $expectedList = <<<EOT
+id-123
+id-456
+EOT;
+        $this->assertFormattedOutputMatches($expectedList, 'list', $data);
+    }
+
+    protected function tableWithAlternativesExampleData()
+    {
+        $data = [
+            'id-123' =>
+            [
+                'one' => 'a',
+                'two' => ['this', 'that', 'the other thing'],
+                'three' => 'c',
+            ],
+            'id-456' =>
+            [
+                'one' => 'x',
+                'two' => 'y',
+                'three' => ['apples', 'oranges'],
+            ],
+        ];
+        return new RowsOfFieldsWithAlternatives($data);
+    }
+
+    function testTableWithAlternatives()
+    {
+        $data = $this->tableWithAlternativesExampleData();
+
+        $expected = <<<EOT
+ ----- --------------------------- ----------------
+  One   Two                         Three
+ ----- --------------------------- ----------------
+  a     this|that|the other thing   c
+  x     y                           apples|oranges
+ ----- --------------------------- ----------------
+EOT;
+        $this->assertFormattedOutputMatches($expected, 'table', $data);
+
+        $expectedBorderless = <<<EOT
+ ===== =========================== ================
+  One   Two                         Three
+ ===== =========================== ================
+  a     this|that|the other thing   c
+  x     y                           apples|oranges
+ ===== =========================== ================
+EOT;
+        $this->assertFormattedOutputMatches($expectedBorderless, 'table', $data, new FormatterOptions(['table-style' => 'borderless']));
+
+        $expectedJson = <<<EOT
+{
+    "id-123": {
+        "one": "a",
+        "two": [
+            "this",
+            "that",
+            "the other thing"
+        ],
+        "three": "c"
+    },
+    "id-456": {
+        "one": "x",
+        "two": "y",
+        "three": [
+            "apples",
+            "oranges"
+        ]
+    }
+}
+EOT;
+        $this->assertFormattedOutputMatches($expectedJson, 'json', $data);
+
+        $expectedCsv = <<<EOT
+One,Two,Three
+a,"this|that|the other thing",c
+x,y,apples|oranges
+EOT;
+        $this->assertFormattedOutputMatches($expectedCsv, 'csv', $data);
+
+        $expectedList = <<<EOT
+id-123
+id-456
+EOT;
+        $this->assertFormattedOutputMatches($expectedList, 'list', $data);
+    }
+
+    function testSimpleTableWithFieldLabels()
+    {
+        $data = $this->simpleTableExampleData();
+        $configurationData = new FormatterOptions(
+            [
+                'field-labels' => ['one' => 'Ichi', 'two' => 'Ni', 'three' => 'San'],
+                'row-labels' => ['id-123' => 'Walrus', 'id-456' => 'Carpenter'],
+            ]
+        );
+        $configurationDataAnnotationFormat = new FormatterOptions(
+            [
+                'field-labels' => "one: Uno\ntwo: Dos\nthree: Tres",
+            ]
+        );
+
+        $expected = <<<EOT
+ ------ ---- -----
+  Ichi   Ni   San
+ ------ ---- -----
+  a      b    c
+  x      y    z
+ ------ ---- -----
+EOT;
+        $this->assertFormattedOutputMatches($expected, 'table', $data, $configurationData);
+
+        $expectedSidewaysTable = <<<EOT
+ ------ --- ---
+  Ichi   a   x
+  Ni     b   y
+  San    c   z
+ ------ --- ---
+EOT;
+        $this->assertFormattedOutputMatches($expectedSidewaysTable, 'table', $data, $configurationData->override(['list-orientation' => true]));
+
+        $expectedAnnotationFormatConfigData = <<<EOT
+ ----- ----- ------
+  Uno   Dos   Tres
+ ----- ----- ------
+  a     b     c
+  x     y     z
+ ----- ----- ------
+EOT;
+        $this->assertFormattedOutputMatches($expectedAnnotationFormatConfigData, 'table', $data, $configurationDataAnnotationFormat);
+
+        $expectedWithNoFields = <<<EOT
+ --- --- ---
+  a   b   c
+  x   y   z
+ --- --- ---
+EOT;
+        $this->assertFormattedOutputMatches($expectedWithNoFields, 'table', $data, $configurationData, ['include-field-labels' => false]);
+
+        $expectedWithReorderedFields = <<<EOT
+ ----- ------
+  San   Ichi
+ ----- ------
+  c     a
+  z     x
+ ----- ------
+EOT;
+        $this->assertFormattedOutputMatches($expectedWithReorderedFields, 'table', $data, $configurationData, ['fields' => ['three', 'one']]);
+        $this->assertFormattedOutputMatches($expectedWithReorderedFields, 'table', $data, $configurationData, ['fields' => ['San', 'Ichi']]);
+        $this->assertFormattedOutputMatches($expectedWithReorderedFields, 'table', $data, $configurationData, ['fields' => 'San,Ichi']);
+
+        $expectedWithRegexField = <<<EOT
+ ------ -----
+  Ichi   San
+ ------ -----
+  a      c
+  x      z
+ ------ -----
+EOT;
+        $this->assertFormattedOutputMatches($expectedWithRegexField, 'table', $data, $configurationData, ['fields' => ['/e$/']]);
+        $this->assertFormattedOutputMatches($expectedWithRegexField, 'table', $data, $configurationData, ['fields' => ['*e']]);
+        $this->assertFormattedOutputMatches($expectedWithRegexField, 'table', $data, $configurationData, ['default-fields' => ['*e']]);
+
+        $expectedSections = <<<EOT
+
+Walrus
+ One   a
+ Two   b
+ Three c
+
+Carpenter
+ One   x
+ Two   y
+ Three z
+EOT;
+        $this->assertFormattedOutputMatches($expectedSections, 'sections', $data, $configurationData);
+
+        $expectedJson = <<<EOT
+{
+    "id-123": {
+        "three": "c",
+        "one": "a"
+    },
+    "id-456": {
+        "three": "z",
+        "one": "x"
+    }
+}
+EOT;
+        $this->assertFormattedOutputMatches($expectedJson, 'json', $data, $configurationData, ['fields' => ['San', 'Ichi']]);
+
+        $expectedSingleField = <<<EOT
+ -----
+  San
+ -----
+  c
+  z
+ -----
+EOT;
+        $this->assertFormattedOutputMatches($expectedSingleField, 'table', $data, $configurationData, ['field' => 'San']);
+
+        $expectedEmptyColumn = <<<EOT
+ -----
+  San
+ -----
+EOT;
+
+        $this->assertFormattedOutputMatches($expectedEmptyColumn, 'table', new RowsOfFields([]), $configurationData, ['field' => 'San']);
+
+        $this->assertFormattedOutputMatches('', '', new RowsOfFields([]), $configurationData, ['field' => 'San']);
+        $this->assertFormattedOutputMatches('[]', 'json', new RowsOfFields([]), $configurationData, ['field' => 'San']);
+    }
+
+    /**
+     * @expectedException \Consolidation\OutputFormatters\Exception\UnknownFieldException
+     * @expectedExceptionCode 1
+     * @expectedExceptionMessage The requested field, 'Shi', is not defined.
+     */
+    function testNoSuchFieldException()
+    {
+        $configurationData = new FormatterOptions(
+            [
+                'field-labels' => ['one' => 'Ichi', 'two' => 'Ni', 'three' => 'San'],
+                'row-labels' => ['id-123' => 'Walrus', 'id-456' => 'Carpenter'],
+            ]
+        );
+        $data = $this->simpleTableExampleData();
+        $this->assertFormattedOutputMatches('Will throw before comparing', 'table', $data, $configurationData, ['field' => 'Shi']);
+    }
+
+    protected function simpleListExampleData()
+    {
+        $data = [
+            'one' => 'apple',
+            'two' => 'banana',
+            'three' => 'carrot',
+        ];
+        return new PropertyList($data);
+    }
+
+    // Test with the deprecated data structure
+    protected function simpleListExampleDataUsingAssociativeList()
+    {
+        $data = [
+            'one' => 'apple',
+            'two' => 'banana',
+            'three' => 'carrot',
+        ];
+        return new AssociativeList($data);
+    }
+
+    /**
+     * @expectedException \Consolidation\OutputFormatters\Exception\InvalidFormatException
+     * @expectedExceptionCode 1
+     * @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
+     */
+    function testIncompatibleListDataForTableFormatter()
+    {
+        $data = $this->simpleListExampleData();
+        $this->assertFormattedOutputMatches('Should throw an exception before comparing the table data', 'table', $data->getArrayCopy());
+    }
+
+    function testEmptyList()
+    {
+        $data = new RowsOfFields([]);
+
+        $expected = <<<EOT
+ --- ---- -----
+  I   II   III
+ --- ---- -----
+EOT;
+
+        // If we provide field labels, then the output will change to reflect that.
+        $formatterOptionsWithFieldLables = new FormatterOptions();
+        $formatterOptionsWithFieldLables
+            ->setFieldLabels(['one' => 'I', 'two' => 'II', 'three' => 'III']);
+        $this->assertFormattedOutputMatches($expected, 'table', $data, $formatterOptionsWithFieldLables);
+    }
+
+    function testSimpleList()
+    {
+
+        $expected = <<<EOT
+ ------- --------
+  One     apple
+  Two     banana
+  Three   carrot
+ ------- --------
+EOT;
+        $data = $this->simpleListExampleDataUsingAssociativeList();
+
+        $this->assertFormattedOutputMatches($expected, 'table', $data);
+
+        $data = $this->simpleListExampleData();
+
+        $this->assertFormattedOutputMatches($expected, 'table', $data);
+
+        $expected = <<<EOT
+ ----- --------
+  I     apple
+  II    banana
+  III   carrot
+ ----- --------
+EOT;
+        // If we provide field labels, then the output will change to reflect that.
+        $formatterOptionsWithFieldLables = new FormatterOptions();
+        $formatterOptionsWithFieldLables
+            ->setFieldLabels(['one' => 'I', 'two' => 'II', 'three' => 'III']);
+        $this->assertFormattedOutputMatches($expected, 'table', $data, $formatterOptionsWithFieldLables);
+
+        $expectedDrushStyleTable = <<<EOT
+ One   : apple
+ Two   : banana
+ Three : carrot
+EOT;
+
+        // If we provide field labels, then the output will change to reflect that.
+        $formatterOptionsWithFieldLables = new FormatterOptions();
+        $formatterOptionsWithFieldLables
+            ->setTableStyle('compact')
+            ->setListDelimiter(':');
+        $this->assertFormattedOutputMatches($expectedDrushStyleTable, 'table', $data, $formatterOptionsWithFieldLables);
+
+
+        // Adding an extra field that does not exist in the data set should not change the output
+        $formatterOptionsWithExtraFieldLables = new FormatterOptions();
+        $formatterOptionsWithExtraFieldLables
+            ->setFieldLabels(['one' => 'I', 'two' => 'II', 'three' => 'III', 'four' => 'IV']);
+        $this->assertFormattedOutputMatches($expected, 'table', $data, $formatterOptionsWithExtraFieldLables);
+
+        $expectedRotated = <<<EOT
+ ------- -------- --------
+  One     Two      Three
+ ------- -------- --------
+  apple   banana   carrot
+ ------- -------- --------
+EOT;
+        $this->assertFormattedOutputMatches($expectedRotated, 'table', $data, new FormatterOptions(['list-orientation' => false]));
+
+        $expectedList = <<< EOT
+apple
+banana
+carrot
+EOT;
+        $this->assertFormattedOutputMatches($expectedList, 'list', $data);
+
+        $expectedReorderedList = <<< EOT
+carrot
+apple
+EOT;
+        $options = new FormatterOptions([FormatterOptions::FIELDS => 'three,one']);
+        $this->assertFormattedOutputMatches($expectedReorderedList, 'list', $data, $options);
+
+        $expectedCsv = <<< EOT
+One,Two,Three
+apple,banana,carrot
+EOT;
+        $this->assertFormattedOutputMatches($expectedCsv, 'csv', $data);
+
+        $expectedCsvNoHeaders = 'apple,banana,carrot';
+        $this->assertFormattedOutputMatches($expectedCsvNoHeaders, 'csv', $data, new FormatterOptions(), ['include-field-labels' => false]);
+
+        // Next, configure the formatter options with 'include-field-labels',
+        // but set --include-field-labels to turn the option back on again.
+        $options = new FormatterOptions(['include-field-labels' => false]);
+        $input = new StringInput('test --include-field-labels');
+        $optionDefinitions = [
+            new InputArgument('unused', InputArgument::REQUIRED),
+            new InputOption('include-field-labels', null, InputOption::VALUE_NONE),
+        ];
+        $definition = new InputDefinition($optionDefinitions);
+        $input->bind($definition);
+        $testValue = $input->getOption('include-field-labels');
+        $this->assertTrue($testValue);
+        $hasFieldLabels = $input->hasOption('include-field-labels');
+        $this->assertTrue($hasFieldLabels);
+
+        $this->assertFormattedOutputMatches($expectedCsvNoHeaders, 'csv', $data, $options);
+        $options->setInput($input);
+        $this->assertFormattedOutputMatches($expectedCsv, 'csv', $data, $options);
+    }
+
+    protected function associativeListWithRenderer()
+    {
+        $data = [
+            'one' => 'apple',
+            'two' => ['banana', 'plantain'],
+            'three' => 'carrot',
+            'four' => ['peaches', 'pumpkin pie'],
+        ];
+        $list = new PropertyList($data);
+
+        $list->addRendererFunction(
+            function ($key, $cellData, FormatterOptions $options)
+            {
+                if (is_array($cellData)) {
+                    return implode(',', $cellData);
+                }
+                return $cellData;
+            }
+        );
+
+        return $list;
+    }
+
+    protected function associativeListWithCsvCells()
+    {
+        $data = [
+            'one' => 'apple',
+            'two' => ['banana', 'plantain'],
+            'three' => 'carrot',
+            'four' => ['peaches', 'pumpkin pie'],
+        ];
+        return new PropertyListWithCsvCells($data);
+    }
+
+    function testPropertyListWithCsvCells()
+    {
+        $this->doPropertyListWithCsvCells($this->associativeListWithRenderer());
+        $this->doPropertyListWithCsvCells($this->associativeListWithCsvCells());
+    }
+
+    function doPropertyListWithCsvCells($data)
+    {
+        $expected = <<<EOT
+ ------- ---------------------
+  One     apple
+  Two     banana,plantain
+  Three   carrot
+  Four    peaches,pumpkin pie
+ ------- ---------------------
+EOT;
+        $this->assertFormattedOutputMatches($expected, 'table', $data);
+
+        $expectedList = <<< EOT
+apple
+banana,plantain
+carrot
+peaches,pumpkin pie
+EOT;
+        $this->assertFormattedOutputMatches($expectedList, 'list', $data);
+
+        $expectedCsv = <<< EOT
+One,Two,Three,Four
+apple,"banana,plantain",carrot,"peaches,pumpkin pie"
+EOT;
+        $this->assertFormattedOutputMatches($expectedCsv, 'csv', $data);
+
+        $expectedCsvNoHeaders = 'apple,"banana,plantain",carrot,"peaches,pumpkin pie"';
+        $this->assertFormattedOutputMatches($expectedCsvNoHeaders, 'csv', $data, new FormatterOptions(), ['include-field-labels' => false]);
+
+        $expectedTsv = <<< EOT
+apple\tbanana,plantain\tcarrot\tpeaches,pumpkin pie
+EOT;
+        $this->assertFormattedOutputMatches($expectedTsv, 'tsv', $data);
+
+    }
+
+    function testSimpleListWithFieldLabels()
+    {
+        $data = $this->simpleListExampleData();
+        $configurationData = new FormatterOptions(
+            [
+                'field-labels' => ['one' => 'Ichi', 'two' => 'Ni', 'three' => 'San'],
+            ]
+        );
+
+        $expected = <<<EOT
+ ------ --------
+  Ichi   apple
+  Ni     banana
+  San    carrot
+ ------ --------
+EOT;
+        $this->assertFormattedOutputMatches($expected, 'table', $data, $configurationData);
+
+        $expectedWithReorderedFields = <<<EOT
+ ------ --------
+  San    carrot
+  Ichi   apple
+ ------ --------
+EOT;
+        $this->assertFormattedOutputMatches($expectedWithReorderedFields, 'table', $data, $configurationData, ['fields' => ['three', 'one']]);
+        $this->assertFormattedOutputMatches($expectedWithReorderedFields, 'table', $data, $configurationData, ['fields' => ['San', 'Ichi']]);
+
+        $expectedJson = <<<EOT
+{
+    "three": "carrot",
+    "one": "apple"
+}
+EOT;
+        $this->assertFormattedOutputMatches($expectedJson, 'json', $data, $configurationData, ['fields' => ['San', 'Ichi']]);
+    }
+
+    function testSimpleXml()
+    {
+        $data = [
+            'name' => 'primary',
+            'description' => 'The primary colors of the color wheel.',
+            'colors' =>
+            [
+                'red',
+                'yellow',
+                'blue',
+            ],
+        ];
+
+        $expected = <<<EOT
+<?xml version="1.0" encoding="UTF-8"?>
+<document name="primary">
+  <description>The primary colors of the color wheel.</description>
+  <colors>
+    <color>red</color>
+    <color>yellow</color>
+    <color>blue</color>
+  </colors>
+</document>
+EOT;
+
+        $this->assertFormattedOutputMatches($expected, 'xml', $data);
+    }
+
+    function domDocumentData()
+    {
+        $dom = new \DOMDocument('1.0', 'UTF-8');
+
+        $document = $dom->createElement('document');
+        $dom->appendChild($document);
+
+        $document->setAttribute('name', 'primary');
+        $description = $dom->createElement('description');
+        $document->appendChild($description);
+        $description->appendChild($dom->createTextNode('The primary colors of the color wheel.'));
+
+        $this->domCreateElements($dom, $document, 'color', ['red', 'yellow', 'blue']);
+
+        return $dom;
+    }
+
+    function domCreateElements($dom, $element, $name, $data)
+    {
+        $container = $dom->createElement("{$name}s");
+        $element->appendChild($container);
+        foreach ($data as $value) {
+            $child = $dom->createElement($name);
+            $container->appendChild($child);
+            $child->appendChild($dom->createTextNode($value));
+        }
+    }
+
+    function complexDomDocumentData()
+    {
+        $dom = new \DOMDocument('1.0', 'UTF-8');
+
+        $document = $dom->createElement('document');
+        $dom->appendChild($document);
+
+        $document->setAttribute('name', 'widget-collection');
+        $description = $dom->createElement('description');
+        $document->appendChild($description);
+        $description->appendChild($dom->createTextNode('A couple of widgets.'));
+
+        $widgets = $dom->createElement('widgets');
+        $document->appendChild($widgets);
+
+        $widget = $dom->createElement('widget');
+        $widgets->appendChild($widget);
+        $widget->setAttribute('name', 'usual');
+        $this->domCreateElements($dom, $widget, 'color', ['red', 'yellow', 'blue']);
+        $this->domCreateElements($dom, $widget, 'shape', ['square', 'circle', 'triangle']);
+
+        $widget = $dom->createElement('widget');
+        $widgets->appendChild($widget);
+        $widget->setAttribute('name', 'unusual');
+        $this->domCreateElements($dom, $widget, 'color', ['muave', 'puce', 'umber']);
+        $this->domCreateElements($dom, $widget, 'shape', ['elipse', 'rhombus', 'trapazoid']);
+
+        return $dom;
+    }
+
+    function domDocumentTestValues()
+    {
+
+        $expectedXml = <<<EOT
+<?xml version="1.0" encoding="UTF-8"?>
+<document name="primary">
+  <description>The primary colors of the color wheel.</description>
+  <colors>
+    <color>red</color>
+    <color>yellow</color>
+    <color>blue</color>
+  </colors>
+</document>
+EOT;
+
+        $expectedJson = <<<EOT
+{
+    "name": "primary",
+    "description": "The primary colors of the color wheel.",
+    "colors": [
+        "red",
+        "yellow",
+        "blue"
+    ]
+}
+EOT;
+
+        $expectedComplexXml = <<<EOT
+<?xml version="1.0" encoding="UTF-8"?>
+<document name="widget-collection">
+  <description>A couple of widgets.</description>
+  <widgets>
+    <widget name="usual">
+      <colors>
+        <color>red</color>
+        <color>yellow</color>
+        <color>blue</color>
+      </colors>
+      <shapes>
+        <shape>square</shape>
+        <shape>circle</shape>
+        <shape>triangle</shape>
+      </shapes>
+    </widget>
+    <widget name="unusual">
+      <colors>
+        <color>muave</color>
+        <color>puce</color>
+        <color>umber</color>
+      </colors>
+      <shapes>
+        <shape>elipse</shape>
+        <shape>rhombus</shape>
+        <shape>trapazoid</shape>
+      </shapes>
+    </widget>
+  </widgets>
+</document>
+EOT;
+
+        $expectedComplexJson = <<<EOT
+{
+    "name": "widget-collection",
+    "description": "A couple of widgets.",
+    "widgets": {
+        "usual": {
+            "name": "usual",
+            "colors": [
+                "red",
+                "yellow",
+                "blue"
+            ],
+            "shapes": [
+                "square",
+                "circle",
+                "triangle"
+            ]
+        },
+        "unusual": {
+            "name": "unusual",
+            "colors": [
+                "muave",
+                "puce",
+                "umber"
+            ],
+            "shapes": [
+                "elipse",
+                "rhombus",
+                "trapazoid"
+            ]
+        }
+    }
+}
+EOT;
+
+        return [
+            [
+                $this->domDocumentData(),
+                $expectedXml,
+                $expectedJson,
+            ],
+            [
+                $this->complexDomDocumentData(),
+                $expectedComplexXml,
+                $expectedComplexJson,
+            ],
+        ];
+    }
+
+    /**
+     *  @dataProvider domDocumentTestValues
+     */
+    function testDomData($data, $expectedXml, $expectedJson)
+    {
+        $this->assertFormattedOutputMatches($expectedXml, 'xml', $data);
+        $this->assertFormattedOutputMatches($expectedJson, 'json', $data);
+
+        // Check to see if we get the same xml data if we convert from
+        // DOM -> array -> DOM.
+        $expectedJsonAsArray = (array)json_decode($expectedJson);
+        $this->assertFormattedOutputMatches($expectedXml, 'xml', $expectedJsonAsArray);
+    }
+
+    /**
+     * @expectedException \Exception
+     * @expectedExceptionCode 1
+     * @expectedExceptionMessage Data provided to Consolidation\OutputFormatters\Formatters\XmlFormatter must be either an instance of DOMDocument or an array. Instead, a string was provided.
+     */
+    function testDataTypeForXmlFormatter()
+    {
+        $this->assertFormattedOutputMatches('Will fail, not return', 'xml', 'Strings cannot be converted to XML');
+    }
+}