TableTest.php 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Console\Tests\Helper;
  11. use PHPUnit\Framework\TestCase;
  12. use Symfony\Component\Console\Formatter\OutputFormatter;
  13. use Symfony\Component\Console\Helper\Table;
  14. use Symfony\Component\Console\Helper\TableCell;
  15. use Symfony\Component\Console\Helper\TableSeparator;
  16. use Symfony\Component\Console\Helper\TableStyle;
  17. use Symfony\Component\Console\Output\ConsoleSectionOutput;
  18. use Symfony\Component\Console\Output\StreamOutput;
  19. class TableTest extends TestCase
  20. {
  21. protected $stream;
  22. protected function setUp()
  23. {
  24. $this->stream = fopen('php://memory', 'r+');
  25. }
  26. protected function tearDown()
  27. {
  28. fclose($this->stream);
  29. $this->stream = null;
  30. }
  31. /**
  32. * @dataProvider renderProvider
  33. */
  34. public function testRender($headers, $rows, $style, $expected, $decorated = false)
  35. {
  36. $table = new Table($output = $this->getOutputStream($decorated));
  37. $table
  38. ->setHeaders($headers)
  39. ->setRows($rows)
  40. ->setStyle($style)
  41. ;
  42. $table->render();
  43. $this->assertEquals($expected, $this->getOutputContent($output));
  44. }
  45. /**
  46. * @dataProvider renderProvider
  47. */
  48. public function testRenderAddRows($headers, $rows, $style, $expected, $decorated = false)
  49. {
  50. $table = new Table($output = $this->getOutputStream($decorated));
  51. $table
  52. ->setHeaders($headers)
  53. ->addRows($rows)
  54. ->setStyle($style)
  55. ;
  56. $table->render();
  57. $this->assertEquals($expected, $this->getOutputContent($output));
  58. }
  59. /**
  60. * @dataProvider renderProvider
  61. */
  62. public function testRenderAddRowsOneByOne($headers, $rows, $style, $expected, $decorated = false)
  63. {
  64. $table = new Table($output = $this->getOutputStream($decorated));
  65. $table
  66. ->setHeaders($headers)
  67. ->setStyle($style)
  68. ;
  69. foreach ($rows as $row) {
  70. $table->addRow($row);
  71. }
  72. $table->render();
  73. $this->assertEquals($expected, $this->getOutputContent($output));
  74. }
  75. public function renderProvider()
  76. {
  77. $books = [
  78. ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'],
  79. ['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'],
  80. ['960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'],
  81. ['80-902734-1-6', 'And Then There Were None', 'Agatha Christie'],
  82. ];
  83. return [
  84. [
  85. ['ISBN', 'Title', 'Author'],
  86. $books,
  87. 'default',
  88. <<<'TABLE'
  89. +---------------+--------------------------+------------------+
  90. | ISBN | Title | Author |
  91. +---------------+--------------------------+------------------+
  92. | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
  93. | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
  94. | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
  95. | 80-902734-1-6 | And Then There Were None | Agatha Christie |
  96. +---------------+--------------------------+------------------+
  97. TABLE
  98. ],
  99. [
  100. ['ISBN', 'Title', 'Author'],
  101. $books,
  102. 'compact',
  103. <<<'TABLE'
  104. ISBN Title Author
  105. 99921-58-10-7 Divine Comedy Dante Alighieri
  106. 9971-5-0210-0 A Tale of Two Cities Charles Dickens
  107. 960-425-059-0 The Lord of the Rings J. R. R. Tolkien
  108. 80-902734-1-6 And Then There Were None Agatha Christie
  109. TABLE
  110. ],
  111. [
  112. ['ISBN', 'Title', 'Author'],
  113. $books,
  114. 'borderless',
  115. <<<'TABLE'
  116. =============== ========================== ==================
  117. ISBN Title Author
  118. =============== ========================== ==================
  119. 99921-58-10-7 Divine Comedy Dante Alighieri
  120. 9971-5-0210-0 A Tale of Two Cities Charles Dickens
  121. 960-425-059-0 The Lord of the Rings J. R. R. Tolkien
  122. 80-902734-1-6 And Then There Were None Agatha Christie
  123. =============== ========================== ==================
  124. TABLE
  125. ],
  126. [
  127. ['ISBN', 'Title', 'Author'],
  128. $books,
  129. 'box',
  130. <<<'TABLE'
  131. ┌───────────────┬──────────────────────────┬──────────────────┐
  132. │ ISBN │ Title │ Author │
  133. ├───────────────┼──────────────────────────┼──────────────────┤
  134. │ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri │
  135. │ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens │
  136. │ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien │
  137. │ 80-902734-1-6 │ And Then There Were None │ Agatha Christie │
  138. └───────────────┴──────────────────────────┴──────────────────┘
  139. TABLE
  140. ],
  141. [
  142. ['ISBN', 'Title', 'Author'],
  143. [
  144. ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'],
  145. ['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'],
  146. new TableSeparator(),
  147. ['960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'],
  148. ['80-902734-1-6', 'And Then There Were None', 'Agatha Christie'],
  149. ],
  150. 'box-double',
  151. <<<'TABLE'
  152. ╔═══════════════╤══════════════════════════╤══════════════════╗
  153. ║ ISBN │ Title │ Author ║
  154. ╠═══════════════╪══════════════════════════╪══════════════════╣
  155. ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
  156. ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
  157. ╟───────────────┼──────────────────────────┼──────────────────╢
  158. ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
  159. ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
  160. ╚═══════════════╧══════════════════════════╧══════════════════╝
  161. TABLE
  162. ],
  163. [
  164. ['ISBN', 'Title'],
  165. [
  166. ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'],
  167. ['9971-5-0210-0'],
  168. ['960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'],
  169. ['80-902734-1-6', 'And Then There Were None', 'Agatha Christie'],
  170. ],
  171. 'default',
  172. <<<'TABLE'
  173. +---------------+--------------------------+------------------+
  174. | ISBN | Title | |
  175. +---------------+--------------------------+------------------+
  176. | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
  177. | 9971-5-0210-0 | | |
  178. | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
  179. | 80-902734-1-6 | And Then There Were None | Agatha Christie |
  180. +---------------+--------------------------+------------------+
  181. TABLE
  182. ],
  183. [
  184. [],
  185. [
  186. ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'],
  187. ['9971-5-0210-0'],
  188. ['960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'],
  189. ['80-902734-1-6', 'And Then There Were None', 'Agatha Christie'],
  190. ],
  191. 'default',
  192. <<<'TABLE'
  193. +---------------+--------------------------+------------------+
  194. | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
  195. | 9971-5-0210-0 | | |
  196. | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
  197. | 80-902734-1-6 | And Then There Were None | Agatha Christie |
  198. +---------------+--------------------------+------------------+
  199. TABLE
  200. ],
  201. [
  202. ['ISBN', 'Title', 'Author'],
  203. [
  204. ['99921-58-10-7', "Divine\nComedy", 'Dante Alighieri'],
  205. ['9971-5-0210-2', "Harry Potter\nand the Chamber of Secrets", "Rowling\nJoanne K."],
  206. ['9971-5-0210-2', "Harry Potter\nand the Chamber of Secrets", "Rowling\nJoanne K."],
  207. ['960-425-059-0', 'The Lord of the Rings', "J. R. R.\nTolkien"],
  208. ],
  209. 'default',
  210. <<<'TABLE'
  211. +---------------+----------------------------+-----------------+
  212. | ISBN | Title | Author |
  213. +---------------+----------------------------+-----------------+
  214. | 99921-58-10-7 | Divine | Dante Alighieri |
  215. | | Comedy | |
  216. | 9971-5-0210-2 | Harry Potter | Rowling |
  217. | | and the Chamber of Secrets | Joanne K. |
  218. | 9971-5-0210-2 | Harry Potter | Rowling |
  219. | | and the Chamber of Secrets | Joanne K. |
  220. | 960-425-059-0 | The Lord of the Rings | J. R. R. |
  221. | | | Tolkien |
  222. +---------------+----------------------------+-----------------+
  223. TABLE
  224. ],
  225. [
  226. ['ISBN', 'Title'],
  227. [],
  228. 'default',
  229. <<<'TABLE'
  230. +------+-------+
  231. | ISBN | Title |
  232. +------+-------+
  233. TABLE
  234. ],
  235. [
  236. [],
  237. [],
  238. 'default',
  239. '',
  240. ],
  241. 'Cell text with tags used for Output styling' => [
  242. ['ISBN', 'Title', 'Author'],
  243. [
  244. ['<info>99921-58-10-7</info>', '<error>Divine Comedy</error>', '<fg=blue;bg=white>Dante Alighieri</fg=blue;bg=white>'],
  245. ['9971-5-0210-0', 'A Tale of Two Cities', '<info>Charles Dickens</>'],
  246. ],
  247. 'default',
  248. <<<'TABLE'
  249. +---------------+----------------------+-----------------+
  250. | ISBN | Title | Author |
  251. +---------------+----------------------+-----------------+
  252. | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
  253. | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
  254. +---------------+----------------------+-----------------+
  255. TABLE
  256. ],
  257. 'Cell text with tags not used for Output styling' => [
  258. ['ISBN', 'Title', 'Author'],
  259. [
  260. ['<strong>99921-58-10-700</strong>', '<f>Divine Com</f>', 'Dante Alighieri'],
  261. ['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'],
  262. ],
  263. 'default',
  264. <<<'TABLE'
  265. +----------------------------------+----------------------+-----------------+
  266. | ISBN | Title | Author |
  267. +----------------------------------+----------------------+-----------------+
  268. | <strong>99921-58-10-700</strong> | <f>Divine Com</f> | Dante Alighieri |
  269. | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
  270. +----------------------------------+----------------------+-----------------+
  271. TABLE
  272. ],
  273. 'Cell with colspan' => [
  274. ['ISBN', 'Title', 'Author'],
  275. [
  276. ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'],
  277. new TableSeparator(),
  278. [new TableCell('Divine Comedy(Dante Alighieri)', ['colspan' => 3])],
  279. new TableSeparator(),
  280. [
  281. new TableCell('Arduino: A Quick-Start Guide', ['colspan' => 2]),
  282. 'Mark Schmidt',
  283. ],
  284. new TableSeparator(),
  285. [
  286. '9971-5-0210-0',
  287. new TableCell("A Tale of \nTwo Cities", ['colspan' => 2]),
  288. ],
  289. new TableSeparator(),
  290. [
  291. new TableCell('Cupiditate dicta atque porro, tempora exercitationem modi animi nulla nemo vel nihil!', ['colspan' => 3]),
  292. ],
  293. ],
  294. 'default',
  295. <<<'TABLE'
  296. +-------------------------------+-------------------------------+-----------------------------+
  297. | ISBN | Title | Author |
  298. +-------------------------------+-------------------------------+-----------------------------+
  299. | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
  300. +-------------------------------+-------------------------------+-----------------------------+
  301. | Divine Comedy(Dante Alighieri) |
  302. +-------------------------------+-------------------------------+-----------------------------+
  303. | Arduino: A Quick-Start Guide | Mark Schmidt |
  304. +-------------------------------+-------------------------------+-----------------------------+
  305. | 9971-5-0210-0 | A Tale of |
  306. | | Two Cities |
  307. +-------------------------------+-------------------------------+-----------------------------+
  308. | Cupiditate dicta atque porro, tempora exercitationem modi animi nulla nemo vel nihil! |
  309. +-------------------------------+-------------------------------+-----------------------------+
  310. TABLE
  311. ],
  312. 'Cell with rowspan' => [
  313. ['ISBN', 'Title', 'Author'],
  314. [
  315. [
  316. new TableCell('9971-5-0210-0', ['rowspan' => 3]),
  317. new TableCell('Divine Comedy', ['rowspan' => 2]),
  318. 'Dante Alighieri',
  319. ],
  320. [],
  321. ["The Lord of \nthe Rings", "J. R. \nR. Tolkien"],
  322. new TableSeparator(),
  323. ['80-902734-1-6', new TableCell("And Then \nThere \nWere None", ['rowspan' => 3]), 'Agatha Christie'],
  324. ['80-902734-1-7', 'Test'],
  325. ],
  326. 'default',
  327. <<<'TABLE'
  328. +---------------+---------------+-----------------+
  329. | ISBN | Title | Author |
  330. +---------------+---------------+-----------------+
  331. | 9971-5-0210-0 | Divine Comedy | Dante Alighieri |
  332. | | | |
  333. | | The Lord of | J. R. |
  334. | | the Rings | R. Tolkien |
  335. +---------------+---------------+-----------------+
  336. | 80-902734-1-6 | And Then | Agatha Christie |
  337. | 80-902734-1-7 | There | Test |
  338. | | Were None | |
  339. +---------------+---------------+-----------------+
  340. TABLE
  341. ],
  342. 'Cell with rowspan and colspan' => [
  343. ['ISBN', 'Title', 'Author'],
  344. [
  345. [
  346. new TableCell('9971-5-0210-0', ['rowspan' => 2, 'colspan' => 2]),
  347. 'Dante Alighieri',
  348. ],
  349. ['Charles Dickens'],
  350. new TableSeparator(),
  351. [
  352. 'Dante Alighieri',
  353. new TableCell('9971-5-0210-0', ['rowspan' => 3, 'colspan' => 2]),
  354. ],
  355. ['J. R. R. Tolkien'],
  356. ['J. R. R'],
  357. ],
  358. 'default',
  359. <<<'TABLE'
  360. +------------------+---------+-----------------+
  361. | ISBN | Title | Author |
  362. +------------------+---------+-----------------+
  363. | 9971-5-0210-0 | Dante Alighieri |
  364. | | Charles Dickens |
  365. +------------------+---------+-----------------+
  366. | Dante Alighieri | 9971-5-0210-0 |
  367. | J. R. R. Tolkien | |
  368. | J. R. R | |
  369. +------------------+---------+-----------------+
  370. TABLE
  371. ],
  372. 'Cell with rowspan and colspan contains new line break' => [
  373. ['ISBN', 'Title', 'Author'],
  374. [
  375. [
  376. new TableCell("9971\n-5-\n021\n0-0", ['rowspan' => 2, 'colspan' => 2]),
  377. 'Dante Alighieri',
  378. ],
  379. ['Charles Dickens'],
  380. new TableSeparator(),
  381. [
  382. 'Dante Alighieri',
  383. new TableCell("9971\n-5-\n021\n0-0", ['rowspan' => 2, 'colspan' => 2]),
  384. ],
  385. ['Charles Dickens'],
  386. new TableSeparator(),
  387. [
  388. new TableCell("9971\n-5-\n021\n0-0", ['rowspan' => 2, 'colspan' => 2]),
  389. new TableCell("Dante \nAlighieri", ['rowspan' => 2, 'colspan' => 1]),
  390. ],
  391. ],
  392. 'default',
  393. <<<'TABLE'
  394. +-----------------+-------+-----------------+
  395. | ISBN | Title | Author |
  396. +-----------------+-------+-----------------+
  397. | 9971 | Dante Alighieri |
  398. | -5- | Charles Dickens |
  399. | 021 | |
  400. | 0-0 | |
  401. +-----------------+-------+-----------------+
  402. | Dante Alighieri | 9971 |
  403. | Charles Dickens | -5- |
  404. | | 021 |
  405. | | 0-0 |
  406. +-----------------+-------+-----------------+
  407. | 9971 | Dante |
  408. | -5- | Alighieri |
  409. | 021 | |
  410. | 0-0 | |
  411. +-----------------+-------+-----------------+
  412. TABLE
  413. ],
  414. 'Cell with rowspan and colspan without using TableSeparator' => [
  415. ['ISBN', 'Title', 'Author'],
  416. [
  417. [
  418. new TableCell("9971\n-5-\n021\n0-0", ['rowspan' => 2, 'colspan' => 2]),
  419. 'Dante Alighieri',
  420. ],
  421. ['Charles Dickens'],
  422. [
  423. 'Dante Alighieri',
  424. new TableCell("9971\n-5-\n021\n0-0", ['rowspan' => 2, 'colspan' => 2]),
  425. ],
  426. ['Charles Dickens'],
  427. ],
  428. 'default',
  429. <<<'TABLE'
  430. +-----------------+-------+-----------------+
  431. | ISBN | Title | Author |
  432. +-----------------+-------+-----------------+
  433. | 9971 | Dante Alighieri |
  434. | -5- | Charles Dickens |
  435. | 021 | |
  436. | 0-0 | |
  437. | Dante Alighieri | 9971 |
  438. | Charles Dickens | -5- |
  439. | | 021 |
  440. | | 0-0 |
  441. +-----------------+-------+-----------------+
  442. TABLE
  443. ],
  444. 'Cell with rowspan and colspan with separator inside a rowspan' => [
  445. ['ISBN', 'Author'],
  446. [
  447. [
  448. new TableCell('9971-5-0210-0', ['rowspan' => 3, 'colspan' => 1]),
  449. 'Dante Alighieri',
  450. ],
  451. [new TableSeparator()],
  452. ['Charles Dickens'],
  453. ],
  454. 'default',
  455. <<<'TABLE'
  456. +---------------+-----------------+
  457. | ISBN | Author |
  458. +---------------+-----------------+
  459. | 9971-5-0210-0 | Dante Alighieri |
  460. | |-----------------|
  461. | | Charles Dickens |
  462. +---------------+-----------------+
  463. TABLE
  464. ],
  465. 'Multiple header lines' => [
  466. [
  467. [new TableCell('Main title', ['colspan' => 3])],
  468. ['ISBN', 'Title', 'Author'],
  469. ],
  470. [],
  471. 'default',
  472. <<<'TABLE'
  473. +------+-------+--------+
  474. | Main title |
  475. +------+-------+--------+
  476. | ISBN | Title | Author |
  477. +------+-------+--------+
  478. TABLE
  479. ],
  480. 'Row with multiple cells' => [
  481. [],
  482. [
  483. [
  484. new TableCell('1', ['colspan' => 3]),
  485. new TableCell('2', ['colspan' => 2]),
  486. new TableCell('3', ['colspan' => 2]),
  487. new TableCell('4', ['colspan' => 2]),
  488. ],
  489. ],
  490. 'default',
  491. <<<'TABLE'
  492. +---+--+--+---+--+---+--+---+--+
  493. | 1 | 2 | 3 | 4 |
  494. +---+--+--+---+--+---+--+---+--+
  495. TABLE
  496. ],
  497. 'Coslpan and table cells with comment style' => [
  498. [
  499. new TableCell('<comment>Long Title</comment>', ['colspan' => 3]),
  500. ],
  501. [
  502. [
  503. new TableCell('9971-5-0210-0', ['colspan' => 3]),
  504. ],
  505. new TableSeparator(),
  506. [
  507. 'Dante Alighieri',
  508. 'J. R. R. Tolkien',
  509. 'J. R. R',
  510. ],
  511. ],
  512. 'default',
  513. <<<TABLE
  514. +-----------------+------------------+---------+
  515. |\033[32m \033[39m\033[33mLong Title\033[39m\033[32m \033[39m|
  516. +-----------------+------------------+---------+
  517. | 9971-5-0210-0 |
  518. +-----------------+------------------+---------+
  519. | Dante Alighieri | J. R. R. Tolkien | J. R. R |
  520. +-----------------+------------------+---------+
  521. TABLE
  522. ,
  523. true,
  524. ],
  525. 'Row with formatted cells containing a newline' => [
  526. [],
  527. [
  528. [
  529. new TableCell('<error>Dont break'."\n".'here</error>', ['colspan' => 2]),
  530. ],
  531. new TableSeparator(),
  532. [
  533. 'foo',
  534. new TableCell('<error>Dont break'."\n".'here</error>', ['rowspan' => 2]),
  535. ],
  536. [
  537. 'bar',
  538. ],
  539. ],
  540. 'default',
  541. <<<'TABLE'
  542. +-------+------------+
  543. | Dont break |
  544. | here |
  545. +-------+------------+
  546. | foo | Dont break |
  547. | bar | here |
  548. +-------+------------+
  549. TABLE
  550. ,
  551. true,
  552. ],
  553. ];
  554. }
  555. public function testRenderMultiByte()
  556. {
  557. $table = new Table($output = $this->getOutputStream());
  558. $table
  559. ->setHeaders(['■■'])
  560. ->setRows([[1234]])
  561. ->setStyle('default')
  562. ;
  563. $table->render();
  564. $expected =
  565. <<<'TABLE'
  566. +------+
  567. | ■■ |
  568. +------+
  569. | 1234 |
  570. +------+
  571. TABLE;
  572. $this->assertEquals($expected, $this->getOutputContent($output));
  573. }
  574. public function testTableCellWithNumericIntValue()
  575. {
  576. $table = new Table($output = $this->getOutputStream());
  577. $table->setRows([[new TableCell(12345)]]);
  578. $table->render();
  579. $expected =
  580. <<<'TABLE'
  581. +-------+
  582. | 12345 |
  583. +-------+
  584. TABLE;
  585. $this->assertEquals($expected, $this->getOutputContent($output));
  586. }
  587. public function testTableCellWithNumericFloatValue()
  588. {
  589. $table = new Table($output = $this->getOutputStream());
  590. $table->setRows([[new TableCell(12345.01)]]);
  591. $table->render();
  592. $expected =
  593. <<<'TABLE'
  594. +----------+
  595. | 12345.01 |
  596. +----------+
  597. TABLE;
  598. $this->assertEquals($expected, $this->getOutputContent($output));
  599. }
  600. public function testStyle()
  601. {
  602. $style = new TableStyle();
  603. $style
  604. ->setHorizontalBorderChars('.')
  605. ->setVerticalBorderChars('.')
  606. ->setDefaultCrossingChar('.')
  607. ;
  608. Table::setStyleDefinition('dotfull', $style);
  609. $table = new Table($output = $this->getOutputStream());
  610. $table
  611. ->setHeaders(['Foo'])
  612. ->setRows([['Bar']])
  613. ->setStyle('dotfull');
  614. $table->render();
  615. $expected =
  616. <<<'TABLE'
  617. .......
  618. . Foo .
  619. .......
  620. . Bar .
  621. .......
  622. TABLE;
  623. $this->assertEquals($expected, $this->getOutputContent($output));
  624. }
  625. public function testRowSeparator()
  626. {
  627. $table = new Table($output = $this->getOutputStream());
  628. $table
  629. ->setHeaders(['Foo'])
  630. ->setRows([
  631. ['Bar1'],
  632. new TableSeparator(),
  633. ['Bar2'],
  634. new TableSeparator(),
  635. ['Bar3'],
  636. ]);
  637. $table->render();
  638. $expected =
  639. <<<'TABLE'
  640. +------+
  641. | Foo |
  642. +------+
  643. | Bar1 |
  644. +------+
  645. | Bar2 |
  646. +------+
  647. | Bar3 |
  648. +------+
  649. TABLE;
  650. $this->assertEquals($expected, $this->getOutputContent($output));
  651. $this->assertEquals($table, $table->addRow(new TableSeparator()), 'fluent interface on addRow() with a single TableSeparator() works');
  652. }
  653. public function testRenderMultiCalls()
  654. {
  655. $table = new Table($output = $this->getOutputStream());
  656. $table->setRows([
  657. [new TableCell('foo', ['colspan' => 2])],
  658. ]);
  659. $table->render();
  660. $table->render();
  661. $table->render();
  662. $expected =
  663. <<<TABLE
  664. +----+---+
  665. | foo |
  666. +----+---+
  667. +----+---+
  668. | foo |
  669. +----+---+
  670. +----+---+
  671. | foo |
  672. +----+---+
  673. TABLE;
  674. $this->assertEquals($expected, $this->getOutputContent($output));
  675. }
  676. public function testColumnStyle()
  677. {
  678. $table = new Table($output = $this->getOutputStream());
  679. $table
  680. ->setHeaders(['ISBN', 'Title', 'Author', 'Price'])
  681. ->setRows([
  682. ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'],
  683. ['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25'],
  684. ]);
  685. $style = new TableStyle();
  686. $style->setPadType(STR_PAD_LEFT);
  687. $table->setColumnStyle(3, $style);
  688. $table->render();
  689. $expected =
  690. <<<TABLE
  691. +---------------+----------------------+-----------------+--------+
  692. | ISBN | Title | Author | Price |
  693. +---------------+----------------------+-----------------+--------+
  694. | 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
  695. | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 139.25 |
  696. +---------------+----------------------+-----------------+--------+
  697. TABLE;
  698. $this->assertEquals($expected, $this->getOutputContent($output));
  699. }
  700. /**
  701. * @expectedException \Symfony\Component\Console\Exception\InvalidArgumentException
  702. * @expectedExceptionMessage A cell must be a TableCell, a scalar or an object implementing __toString, array given.
  703. */
  704. public function testThrowsWhenTheCellInAnArray()
  705. {
  706. $table = new Table($output = $this->getOutputStream());
  707. $table
  708. ->setHeaders(['ISBN', 'Title', 'Author', 'Price'])
  709. ->setRows([
  710. ['99921-58-10-7', [], 'Dante Alighieri', '9.95'],
  711. ]);
  712. $table->render();
  713. }
  714. public function testColumnWidth()
  715. {
  716. $table = new Table($output = $this->getOutputStream());
  717. $table
  718. ->setHeaders(['ISBN', 'Title', 'Author', 'Price'])
  719. ->setRows([
  720. ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'],
  721. ['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25'],
  722. ])
  723. ->setColumnWidth(0, 15)
  724. ->setColumnWidth(3, 10);
  725. $style = new TableStyle();
  726. $style->setPadType(STR_PAD_LEFT);
  727. $table->setColumnStyle(3, $style);
  728. $table->render();
  729. $expected =
  730. <<<TABLE
  731. +-----------------+----------------------+-----------------+------------+
  732. | ISBN | Title | Author | Price |
  733. +-----------------+----------------------+-----------------+------------+
  734. | 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
  735. | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 139.25 |
  736. +-----------------+----------------------+-----------------+------------+
  737. TABLE;
  738. $this->assertEquals($expected, $this->getOutputContent($output));
  739. }
  740. public function testColumnWidths()
  741. {
  742. $table = new Table($output = $this->getOutputStream());
  743. $table
  744. ->setHeaders(['ISBN', 'Title', 'Author', 'Price'])
  745. ->setRows([
  746. ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'],
  747. ['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25'],
  748. ])
  749. ->setColumnWidths([15, 0, -1, 10]);
  750. $style = new TableStyle();
  751. $style->setPadType(STR_PAD_LEFT);
  752. $table->setColumnStyle(3, $style);
  753. $table->render();
  754. $expected =
  755. <<<TABLE
  756. +-----------------+----------------------+-----------------+------------+
  757. | ISBN | Title | Author | Price |
  758. +-----------------+----------------------+-----------------+------------+
  759. | 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
  760. | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 139.25 |
  761. +-----------------+----------------------+-----------------+------------+
  762. TABLE;
  763. $this->assertEquals($expected, $this->getOutputContent($output));
  764. }
  765. public function testSectionOutput()
  766. {
  767. $sections = [];
  768. $stream = $this->getOutputStream(true);
  769. $output = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
  770. $table = new Table($output);
  771. $table
  772. ->setHeaders(['ISBN', 'Title', 'Author', 'Price'])
  773. ->setRows([
  774. ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'],
  775. ]);
  776. $table->render();
  777. $table->appendRow(['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25']);
  778. $expected =
  779. <<<TABLE
  780. +---------------+---------------+-----------------+-------+
  781. |\033[32m ISBN \033[39m|\033[32m Title \033[39m|\033[32m Author \033[39m|\033[32m Price \033[39m|
  782. +---------------+---------------+-----------------+-------+
  783. | 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
  784. +---------------+---------------+-----------------+-------+
  785. \x1b[5A\x1b[0J+---------------+----------------------+-----------------+--------+
  786. |\033[32m ISBN \033[39m|\033[32m Title \033[39m|\033[32m Author \033[39m|\033[32m Price \033[39m|
  787. +---------------+----------------------+-----------------+--------+
  788. | 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
  789. | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 139.25 |
  790. +---------------+----------------------+-----------------+--------+
  791. TABLE;
  792. $this->assertEquals($expected, $this->getOutputContent($output));
  793. }
  794. public function testSectionOutputDoesntClearIfTableIsntRendered()
  795. {
  796. $sections = [];
  797. $stream = $this->getOutputStream(true);
  798. $output = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
  799. $table = new Table($output);
  800. $table
  801. ->setHeaders(['ISBN', 'Title', 'Author', 'Price'])
  802. ->setRows([
  803. ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'],
  804. ]);
  805. $table->appendRow(['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25']);
  806. $expected =
  807. <<<TABLE
  808. +---------------+----------------------+-----------------+--------+
  809. |\033[32m ISBN \033[39m|\033[32m Title \033[39m|\033[32m Author \033[39m|\033[32m Price \033[39m|
  810. +---------------+----------------------+-----------------+--------+
  811. | 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
  812. | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 139.25 |
  813. +---------------+----------------------+-----------------+--------+
  814. TABLE;
  815. $this->assertEquals($expected, $this->getOutputContent($output));
  816. }
  817. public function testSectionOutputWithoutDecoration()
  818. {
  819. $sections = [];
  820. $stream = $this->getOutputStream();
  821. $output = new ConsoleSectionOutput($stream->getStream(), $sections, $stream->getVerbosity(), $stream->isDecorated(), new OutputFormatter());
  822. $table = new Table($output);
  823. $table
  824. ->setHeaders(['ISBN', 'Title', 'Author', 'Price'])
  825. ->setRows([
  826. ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'],
  827. ]);
  828. $table->render();
  829. $table->appendRow(['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25']);
  830. $expected =
  831. <<<TABLE
  832. +---------------+---------------+-----------------+-------+
  833. | ISBN | Title | Author | Price |
  834. +---------------+---------------+-----------------+-------+
  835. | 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
  836. +---------------+---------------+-----------------+-------+
  837. +---------------+----------------------+-----------------+--------+
  838. | ISBN | Title | Author | Price |
  839. +---------------+----------------------+-----------------+--------+
  840. | 99921-58-10-7 | Divine Comedy | Dante Alighieri | 9.95 |
  841. | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | 139.25 |
  842. +---------------+----------------------+-----------------+--------+
  843. TABLE;
  844. $this->assertEquals($expected, $this->getOutputContent($output));
  845. }
  846. /**
  847. * @expectedException \Symfony\Component\Console\Exception\RuntimeException
  848. * @expectedExceptionMessage Output should be an instance of "Symfony\Component\Console\Output\ConsoleSectionOutput" when calling "Symfony\Component\Console\Helper\Table::appendRow".
  849. */
  850. public function testAppendRowWithoutSectionOutput()
  851. {
  852. $table = new Table($this->getOutputStream());
  853. $table->appendRow(['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25']);
  854. }
  855. /**
  856. * @expectedException \Symfony\Component\Console\Exception\InvalidArgumentException
  857. * @expectedExceptionMessage Style "absent" is not defined.
  858. */
  859. public function testIsNotDefinedStyleException()
  860. {
  861. $table = new Table($this->getOutputStream());
  862. $table->setStyle('absent');
  863. }
  864. /**
  865. * @expectedException \Symfony\Component\Console\Exception\InvalidArgumentException
  866. * @expectedExceptionMessage Style "absent" is not defined.
  867. */
  868. public function testGetStyleDefinition()
  869. {
  870. Table::getStyleDefinition('absent');
  871. }
  872. /**
  873. * @dataProvider renderSetTitle
  874. */
  875. public function testSetTitle($headerTitle, $footerTitle, $style, $expected)
  876. {
  877. (new Table($output = $this->getOutputStream()))
  878. ->setHeaderTitle($headerTitle)
  879. ->setFooterTitle($footerTitle)
  880. ->setHeaders(['ISBN', 'Title', 'Author'])
  881. ->setRows([
  882. ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'],
  883. ['9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'],
  884. ['960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'],
  885. ['80-902734-1-6', 'And Then There Were None', 'Agatha Christie'],
  886. ])
  887. ->setStyle($style)
  888. ->render()
  889. ;
  890. $this->assertEquals($expected, $this->getOutputContent($output));
  891. }
  892. public function renderSetTitle()
  893. {
  894. return [
  895. [
  896. 'Books',
  897. 'Page 1/2',
  898. 'default',
  899. <<<'TABLE'
  900. +---------------+----------- Books --------+------------------+
  901. | ISBN | Title | Author |
  902. +---------------+--------------------------+------------------+
  903. | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
  904. | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
  905. | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
  906. | 80-902734-1-6 | And Then There Were None | Agatha Christie |
  907. +---------------+--------- Page 1/2 -------+------------------+
  908. TABLE
  909. ],
  910. [
  911. 'Books',
  912. 'Page 1/2',
  913. 'box',
  914. <<<'TABLE'
  915. ┌───────────────┬─────────── Books ────────┬──────────────────┐
  916. │ ISBN │ Title │ Author │
  917. ├───────────────┼──────────────────────────┼──────────────────┤
  918. │ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri │
  919. │ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens │
  920. │ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien │
  921. │ 80-902734-1-6 │ And Then There Were None │ Agatha Christie │
  922. └───────────────┴───────── Page 1/2 ───────┴──────────────────┘
  923. TABLE
  924. ],
  925. [
  926. 'Boooooooooooooooooooooooooooooooooooooooooooooooooooooooks',
  927. 'Page 1/999999999999999999999999999999999999999999999999999',
  928. 'default',
  929. <<<'TABLE'
  930. +- Booooooooooooooooooooooooooooooooooooooooooooooooooooo... -+
  931. | ISBN | Title | Author |
  932. +---------------+--------------------------+------------------+
  933. | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
  934. | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
  935. | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
  936. | 80-902734-1-6 | And Then There Were None | Agatha Christie |
  937. +- Page 1/99999999999999999999999999999999999999999999999... -+
  938. TABLE
  939. ],
  940. ];
  941. }
  942. public function testColumnMaxWidths()
  943. {
  944. $table = new Table($output = $this->getOutputStream());
  945. $table
  946. ->setRows([
  947. ['Divine Comedy', 'A Tale of Two Cities', 'The Lord of the Rings', 'And Then There Were None'],
  948. ])
  949. ->setColumnMaxWidth(1, 5)
  950. ->setColumnMaxWidth(2, 10)
  951. ->setColumnMaxWidth(3, 15);
  952. $table->render();
  953. $expected =
  954. <<<TABLE
  955. +---------------+-------+------------+-----------------+
  956. | Divine Comedy | A Tal | The Lord o | And Then There |
  957. | | e of | f the Ring | Were None |
  958. | | Two C | s | |
  959. | | ities | | |
  960. +---------------+-------+------------+-----------------+
  961. TABLE;
  962. $this->assertEquals($expected, $this->getOutputContent($output));
  963. }
  964. public function testColumnMaxWidthsWithTrailingBackslash()
  965. {
  966. (new Table($output = $this->getOutputStream()))
  967. ->setColumnMaxWidth(0, 5)
  968. ->setRows([['1234\6']])
  969. ->render()
  970. ;
  971. $expected =
  972. <<<'TABLE'
  973. +-------+
  974. | 1234\ |
  975. | 6 |
  976. +-------+
  977. TABLE;
  978. $this->assertEquals($expected, $this->getOutputContent($output));
  979. }
  980. public function testBoxedStyleWithColspan()
  981. {
  982. $boxed = new TableStyle();
  983. $boxed
  984. ->setHorizontalBorderChars('─')
  985. ->setVerticalBorderChars('│')
  986. ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├')
  987. ;
  988. $table = new Table($output = $this->getOutputStream());
  989. $table->setStyle($boxed);
  990. $table
  991. ->setHeaders(['ISBN', 'Title', 'Author'])
  992. ->setRows([
  993. ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'],
  994. new TableSeparator(),
  995. [new TableCell('This value spans 3 columns.', ['colspan' => 3])],
  996. ])
  997. ;
  998. $table->render();
  999. $expected =
  1000. <<<TABLE
  1001. ┌───────────────┬───────────────┬─────────────────┐
  1002. │ ISBN │ Title │ Author │
  1003. ├───────────────┼───────────────┼─────────────────┤
  1004. │ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri │
  1005. ├───────────────┼───────────────┼─────────────────┤
  1006. │ This value spans 3 columns. │
  1007. └───────────────┴───────────────┴─────────────────┘
  1008. TABLE;
  1009. $this->assertSame($expected, $this->getOutputContent($output));
  1010. }
  1011. protected function getOutputStream($decorated = false)
  1012. {
  1013. return new StreamOutput($this->stream, StreamOutput::VERBOSITY_NORMAL, $decorated);
  1014. }
  1015. protected function getOutputContent(StreamOutput $output)
  1016. {
  1017. rewind($output->getStream());
  1018. return str_replace(PHP_EOL, "\n", stream_get_contents($output->getStream()));
  1019. }
  1020. public function testWithColspanAndMaxWith(): void
  1021. {
  1022. $table = new Table($output = $this->getOutputStream());
  1023. $table->setColumnMaxWidth(0, 15);
  1024. $table->setColumnMaxWidth(1, 15);
  1025. $table->setColumnMaxWidth(2, 15);
  1026. $table->setRows([
  1027. [new TableCell('Lorem ipsum dolor sit amet, <fg=white;bg=green>consectetur</> adipiscing elit, <fg=white;bg=red>sed</> do <fg=white;bg=red>eiusmod</> tempor', ['colspan' => 3])],
  1028. new TableSeparator(),
  1029. [new TableCell('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor', ['colspan' => 3])],
  1030. new TableSeparator(),
  1031. [new TableCell('Lorem ipsum <fg=white;bg=red>dolor</> sit amet, consectetur ', ['colspan' => 2]), 'hello world'],
  1032. new TableSeparator(),
  1033. ['hello <fg=white;bg=green>world</>', new TableCell('Lorem ipsum dolor sit amet, <fg=white;bg=green>consectetur</> adipiscing elit', ['colspan' => 2])],
  1034. new TableSeparator(),
  1035. ['hello ', new TableCell('world', ['colspan' => 1]), 'Lorem ipsum dolor sit amet, consectetur'],
  1036. new TableSeparator(),
  1037. ['Symfony ', new TableCell('Test', ['colspan' => 1]), 'Lorem <fg=white;bg=green>ipsum</> dolor sit amet, consectetur'],
  1038. ])
  1039. ;
  1040. $table->render();
  1041. $expected =
  1042. <<<TABLE
  1043. +-----------------+-----------------+-----------------+
  1044. | Lorem ipsum dolor sit amet, consectetur adipi |
  1045. | scing elit, sed do eiusmod tempor |
  1046. +-----------------+-----------------+-----------------+
  1047. | Lorem ipsum dolor sit amet, consectetur adipi |
  1048. | scing elit, sed do eiusmod tempor |
  1049. +-----------------+-----------------+-----------------+
  1050. | Lorem ipsum dolor sit amet, co | hello world |
  1051. | nsectetur | |
  1052. +-----------------+-----------------+-----------------+
  1053. | hello world | Lorem ipsum dolor sit amet, co |
  1054. | | nsectetur adipiscing elit |
  1055. +-----------------+-----------------+-----------------+
  1056. | hello | world | Lorem ipsum dol |
  1057. | | | or sit amet, co |
  1058. | | | nsectetur |
  1059. +-----------------+-----------------+-----------------+
  1060. | Symfony | Test | Lorem ipsum dol |
  1061. | | | or sit amet, co |
  1062. | | | nsectetur |
  1063. +-----------------+-----------------+-----------------+
  1064. TABLE;
  1065. $this->assertSame($expected, $this->getOutputContent($output));
  1066. }
  1067. }