TableTest.php 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127
  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 = array(
  78. array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
  79. array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'),
  80. array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
  81. array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'),
  82. );
  83. return array(
  84. array(
  85. array('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. array(
  100. array('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. array(
  112. array('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. array(
  127. array('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. array(
  142. array('ISBN', 'Title', 'Author'),
  143. array(
  144. array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
  145. array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'),
  146. new TableSeparator(),
  147. array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
  148. array('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. array(
  164. array('ISBN', 'Title'),
  165. array(
  166. array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
  167. array('9971-5-0210-0'),
  168. array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
  169. array('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. array(
  184. array(),
  185. array(
  186. array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
  187. array('9971-5-0210-0'),
  188. array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
  189. array('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. array(
  202. array('ISBN', 'Title', 'Author'),
  203. array(
  204. array('99921-58-10-7', "Divine\nComedy", 'Dante Alighieri'),
  205. array('9971-5-0210-2', "Harry Potter\nand the Chamber of Secrets", "Rowling\nJoanne K."),
  206. array('9971-5-0210-2', "Harry Potter\nand the Chamber of Secrets", "Rowling\nJoanne K."),
  207. array('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. array(
  226. array('ISBN', 'Title'),
  227. array(),
  228. 'default',
  229. <<<'TABLE'
  230. +------+-------+
  231. | ISBN | Title |
  232. +------+-------+
  233. TABLE
  234. ),
  235. array(
  236. array(),
  237. array(),
  238. 'default',
  239. '',
  240. ),
  241. 'Cell text with tags used for Output styling' => array(
  242. array('ISBN', 'Title', 'Author'),
  243. array(
  244. array('<info>99921-58-10-7</info>', '<error>Divine Comedy</error>', '<fg=blue;bg=white>Dante Alighieri</fg=blue;bg=white>'),
  245. array('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' => array(
  258. array('ISBN', 'Title', 'Author'),
  259. array(
  260. array('<strong>99921-58-10-700</strong>', '<f>Divine Com</f>', 'Dante Alighieri'),
  261. array('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' => array(
  274. array('ISBN', 'Title', 'Author'),
  275. array(
  276. array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
  277. new TableSeparator(),
  278. array(new TableCell('Divine Comedy(Dante Alighieri)', array('colspan' => 3))),
  279. new TableSeparator(),
  280. array(
  281. new TableCell('Arduino: A Quick-Start Guide', array('colspan' => 2)),
  282. 'Mark Schmidt',
  283. ),
  284. new TableSeparator(),
  285. array(
  286. '9971-5-0210-0',
  287. new TableCell("A Tale of \nTwo Cities", array('colspan' => 2)),
  288. ),
  289. new TableSeparator(),
  290. array(
  291. new TableCell('Cupiditate dicta atque porro, tempora exercitationem modi animi nulla nemo vel nihil!', array('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' => array(
  313. array('ISBN', 'Title', 'Author'),
  314. array(
  315. array(
  316. new TableCell('9971-5-0210-0', array('rowspan' => 3)),
  317. new TableCell('Divine Comedy', array('rowspan' => 2)),
  318. 'Dante Alighieri',
  319. ),
  320. array(),
  321. array("The Lord of \nthe Rings", "J. R. \nR. Tolkien"),
  322. new TableSeparator(),
  323. array('80-902734-1-6', new TableCell("And Then \nThere \nWere None", array('rowspan' => 3)), 'Agatha Christie'),
  324. array('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' => array(
  343. array('ISBN', 'Title', 'Author'),
  344. array(
  345. array(
  346. new TableCell('9971-5-0210-0', array('rowspan' => 2, 'colspan' => 2)),
  347. 'Dante Alighieri',
  348. ),
  349. array('Charles Dickens'),
  350. new TableSeparator(),
  351. array(
  352. 'Dante Alighieri',
  353. new TableCell('9971-5-0210-0', array('rowspan' => 3, 'colspan' => 2)),
  354. ),
  355. array('J. R. R. Tolkien'),
  356. array('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' => array(
  373. array('ISBN', 'Title', 'Author'),
  374. array(
  375. array(
  376. new TableCell("9971\n-5-\n021\n0-0", array('rowspan' => 2, 'colspan' => 2)),
  377. 'Dante Alighieri',
  378. ),
  379. array('Charles Dickens'),
  380. new TableSeparator(),
  381. array(
  382. 'Dante Alighieri',
  383. new TableCell("9971\n-5-\n021\n0-0", array('rowspan' => 2, 'colspan' => 2)),
  384. ),
  385. array('Charles Dickens'),
  386. new TableSeparator(),
  387. array(
  388. new TableCell("9971\n-5-\n021\n0-0", array('rowspan' => 2, 'colspan' => 2)),
  389. new TableCell("Dante \nAlighieri", array('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' => array(
  415. array('ISBN', 'Title', 'Author'),
  416. array(
  417. array(
  418. new TableCell("9971\n-5-\n021\n0-0", array('rowspan' => 2, 'colspan' => 2)),
  419. 'Dante Alighieri',
  420. ),
  421. array('Charles Dickens'),
  422. array(
  423. 'Dante Alighieri',
  424. new TableCell("9971\n-5-\n021\n0-0", array('rowspan' => 2, 'colspan' => 2)),
  425. ),
  426. array('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' => array(
  445. array('ISBN', 'Author'),
  446. array(
  447. array(
  448. new TableCell('9971-5-0210-0', array('rowspan' => 3, 'colspan' => 1)),
  449. 'Dante Alighieri',
  450. ),
  451. array(new TableSeparator()),
  452. array('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' => array(
  466. array(
  467. array(new TableCell('Main title', array('colspan' => 3))),
  468. array('ISBN', 'Title', 'Author'),
  469. ),
  470. array(),
  471. 'default',
  472. <<<'TABLE'
  473. +------+-------+--------+
  474. | Main title |
  475. +------+-------+--------+
  476. | ISBN | Title | Author |
  477. +------+-------+--------+
  478. TABLE
  479. ),
  480. 'Row with multiple cells' => array(
  481. array(),
  482. array(
  483. array(
  484. new TableCell('1', array('colspan' => 3)),
  485. new TableCell('2', array('colspan' => 2)),
  486. new TableCell('3', array('colspan' => 2)),
  487. new TableCell('4', array('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' => array(
  498. array(
  499. new TableCell('<comment>Long Title</comment>', array('colspan' => 3)),
  500. ),
  501. array(
  502. array(
  503. new TableCell('9971-5-0210-0', array('colspan' => 3)),
  504. ),
  505. new TableSeparator(),
  506. array(
  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' => array(
  526. array(),
  527. array(
  528. array(
  529. new TableCell('<error>Dont break'."\n".'here</error>', array('colspan' => 2)),
  530. ),
  531. new TableSeparator(),
  532. array(
  533. 'foo',
  534. new TableCell('<error>Dont break'."\n".'here</error>', array('rowspan' => 2)),
  535. ),
  536. array(
  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(array('■■'))
  560. ->setRows(array(array(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(array(array(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(array(array(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(array('Foo'))
  612. ->setRows(array(array('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(array('Foo'))
  630. ->setRows(array(
  631. array('Bar1'),
  632. new TableSeparator(),
  633. array('Bar2'),
  634. new TableSeparator(),
  635. array('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(array(
  657. array(new TableCell('foo', array('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(array('ISBN', 'Title', 'Author', 'Price'))
  681. ->setRows(array(
  682. array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'),
  683. array('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(array('ISBN', 'Title', 'Author', 'Price'))
  709. ->setRows(array(
  710. array('99921-58-10-7', array(), '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(array('ISBN', 'Title', 'Author', 'Price'))
  719. ->setRows(array(
  720. array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'),
  721. array('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(array('ISBN', 'Title', 'Author', 'Price'))
  745. ->setRows(array(
  746. array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'),
  747. array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens', '139.25'),
  748. ))
  749. ->setColumnWidths(array(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 = array();
  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(array('ISBN', 'Title', 'Author', 'Price'))
  773. ->setRows(array(
  774. array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'),
  775. ));
  776. $table->render();
  777. $table->appendRow(array('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 = array();
  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(array('ISBN', 'Title', 'Author', 'Price'))
  802. ->setRows(array(
  803. array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'),
  804. ));
  805. $table->appendRow(array('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 = array();
  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(array('ISBN', 'Title', 'Author', 'Price'))
  825. ->setRows(array(
  826. array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri', '9.95'),
  827. ));
  828. $table->render();
  829. $table->appendRow(array('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(array('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(array('ISBN', 'Title', 'Author'))
  881. ->setRows(array(
  882. array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
  883. array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'),
  884. array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'),
  885. array('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 array(
  895. array(
  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. array(
  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. array(
  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(array(
  947. array('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 testBoxedStyleWithColspan()
  965. {
  966. $boxed = new TableStyle();
  967. $boxed
  968. ->setHorizontalBorderChars('─')
  969. ->setVerticalBorderChars('│')
  970. ->setCrossingChars('┼', '┌', '┬', '┐', '┤', '┘', '┴', '└', '├')
  971. ;
  972. $table = new Table($output = $this->getOutputStream());
  973. $table->setStyle($boxed);
  974. $table
  975. ->setHeaders(array('ISBN', 'Title', 'Author'))
  976. ->setRows(array(
  977. array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'),
  978. new TableSeparator(),
  979. array(new TableCell('This value spans 3 columns.', array('colspan' => 3))),
  980. ))
  981. ;
  982. $table->render();
  983. $expected =
  984. <<<TABLE
  985. ┌───────────────┬───────────────┬─────────────────┐
  986. │ ISBN │ Title │ Author │
  987. ├───────────────┼───────────────┼─────────────────┤
  988. │ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri │
  989. ├───────────────┼───────────────┼─────────────────┤
  990. │ This value spans 3 columns. │
  991. └───────────────┴───────────────┴─────────────────┘
  992. TABLE;
  993. $this->assertSame($expected, $this->getOutputContent($output));
  994. }
  995. protected function getOutputStream($decorated = false)
  996. {
  997. return new StreamOutput($this->stream, StreamOutput::VERBOSITY_NORMAL, $decorated);
  998. }
  999. protected function getOutputContent(StreamOutput $output)
  1000. {
  1001. rewind($output->getStream());
  1002. return str_replace(PHP_EOL, "\n", stream_get_contents($output->getStream()));
  1003. }
  1004. }