TableTest.php 42 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 = [
  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 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(['ISBN', 'Title', 'Author'])
  976. ->setRows([
  977. ['99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'],
  978. new TableSeparator(),
  979. [new TableCell('This value spans 3 columns.', ['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. }