XmlTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. <?php
  2. /**
  3. * @group Xml
  4. */
  5. class XmlTest extends MediaWikiTestCase {
  6. protected function setUp() {
  7. parent::setUp();
  8. $langObj = Language::factory( 'en' );
  9. $langObj->setNamespaces( [
  10. -2 => 'Media',
  11. -1 => 'Special',
  12. 0 => '',
  13. 1 => 'Talk',
  14. 2 => 'User',
  15. 3 => 'User_talk',
  16. 4 => 'MyWiki',
  17. 5 => 'MyWiki_Talk',
  18. 6 => 'File',
  19. 7 => 'File_talk',
  20. 8 => 'MediaWiki',
  21. 9 => 'MediaWiki_talk',
  22. 10 => 'Template',
  23. 11 => 'Template_talk',
  24. 100 => 'Custom',
  25. 101 => 'Custom_talk',
  26. ] );
  27. $this->setMwGlobals( [
  28. 'wgLang' => $langObj,
  29. 'wgUseMediaWikiUIEverywhere' => false,
  30. ] );
  31. }
  32. protected function tearDown() {
  33. Language::factory( 'en' )->resetNamespaces();
  34. parent::tearDown();
  35. }
  36. /**
  37. * @covers Xml::expandAttributes
  38. */
  39. public function testExpandAttributes() {
  40. $this->assertNull( Xml::expandAttributes( null ),
  41. 'Converting a null list of attributes'
  42. );
  43. $this->assertEquals( '', Xml::expandAttributes( [] ),
  44. 'Converting an empty list of attributes'
  45. );
  46. }
  47. /**
  48. * @covers Xml::expandAttributes
  49. */
  50. public function testExpandAttributesException() {
  51. $this->setExpectedException( MWException::class );
  52. Xml::expandAttributes( 'string' );
  53. }
  54. /**
  55. * @covers Xml::element
  56. */
  57. public function testElementOpen() {
  58. $this->assertEquals(
  59. '<element>',
  60. Xml::element( 'element', null, null ),
  61. 'Opening element with no attributes'
  62. );
  63. }
  64. /**
  65. * @covers Xml::element
  66. */
  67. public function testElementEmpty() {
  68. $this->assertEquals(
  69. '<element />',
  70. Xml::element( 'element', null, '' ),
  71. 'Terminated empty element'
  72. );
  73. }
  74. /**
  75. * @covers Xml::input
  76. */
  77. public function testElementInputCanHaveAValueOfZero() {
  78. $this->assertEquals(
  79. '<input name="name" value="0" />',
  80. Xml::input( 'name', false, 0 ),
  81. 'Input with a value of 0 (T25797)'
  82. );
  83. }
  84. /**
  85. * @covers Xml::element
  86. */
  87. public function testElementEscaping() {
  88. $this->assertEquals(
  89. '<element>"hello &lt;there&gt; your\'s &amp; you"</element>',
  90. Xml::element( 'element', null, '"hello <there> your\'s & you"' ),
  91. 'Element with no attributes and content that needs escaping'
  92. );
  93. }
  94. /**
  95. * @covers Xml::escapeTagsOnly
  96. */
  97. public function testEscapeTagsOnly() {
  98. $this->assertEquals( '&quot;&gt;&lt;', Xml::escapeTagsOnly( '"><' ),
  99. 'replace " > and < with their HTML entitites'
  100. );
  101. }
  102. /**
  103. * @covers Xml::element
  104. */
  105. public function testElementAttributes() {
  106. $this->assertEquals(
  107. '<element key="value" <>="&lt;&gt;">',
  108. Xml::element( 'element', [ 'key' => 'value', '<>' => '<>' ], null ),
  109. 'Element attributes, keys are not escaped'
  110. );
  111. }
  112. /**
  113. * @covers Xml::openElement
  114. */
  115. public function testOpenElement() {
  116. $this->assertEquals(
  117. '<element k="v">',
  118. Xml::openElement( 'element', [ 'k' => 'v' ] ),
  119. 'openElement() shortcut'
  120. );
  121. }
  122. /**
  123. * @covers Xml::closeElement
  124. */
  125. public function testCloseElement() {
  126. $this->assertEquals( '</element>', Xml::closeElement( 'element' ), 'closeElement() shortcut' );
  127. }
  128. public function provideMonthSelector() {
  129. global $wgLang;
  130. $header = '<select name="month" id="month" class="mw-month-selector">';
  131. $header2 = '<select name="month" id="monthSelector" class="mw-month-selector">';
  132. $monthsString = '';
  133. for ( $i = 1; $i < 13; $i++ ) {
  134. $monthName = $wgLang->getMonthName( $i );
  135. $monthsString .= "<option value=\"{$i}\">{$monthName}</option>";
  136. if ( $i !== 12 ) {
  137. $monthsString .= "\n";
  138. }
  139. }
  140. $monthsString2 = str_replace(
  141. '<option value="12">December</option>',
  142. '<option value="12" selected="">December</option>',
  143. $monthsString
  144. );
  145. $end = '</select>';
  146. $allMonths = "<option value=\"AllMonths\">all</option>\n";
  147. return [
  148. [ $header . $monthsString . $end, '', null, 'month' ],
  149. [ $header . $monthsString2 . $end, 12, null, 'month' ],
  150. [ $header2 . $monthsString . $end, '', null, 'monthSelector' ],
  151. [ $header . $allMonths . $monthsString . $end, '', 'AllMonths', 'month' ],
  152. ];
  153. }
  154. /**
  155. * @covers Xml::monthSelector
  156. * @dataProvider provideMonthSelector
  157. */
  158. public function testMonthSelector( $expected, $selected, $allmonths, $id ) {
  159. $this->assertEquals(
  160. $expected,
  161. Xml::monthSelector( $selected, $allmonths, $id )
  162. );
  163. }
  164. /**
  165. * @covers Xml::span
  166. */
  167. public function testSpan() {
  168. $this->assertEquals(
  169. '<span class="foo" id="testSpan">element</span>',
  170. Xml::span( 'element', 'foo', [ 'id' => 'testSpan' ] )
  171. );
  172. }
  173. /**
  174. * @covers Xml::dateMenu
  175. */
  176. public function testDateMenu() {
  177. $curYear = intval( gmdate( 'Y' ) );
  178. $prevYear = $curYear - 1;
  179. $curMonth = intval( gmdate( 'n' ) );
  180. $nextMonth = $curMonth + 1;
  181. if ( $nextMonth == 13 ) {
  182. $nextMonth = 1;
  183. }
  184. $this->assertEquals(
  185. '<label for="year">From year (and earlier):</label> ' .
  186. '<input id="year" maxlength="4" size="7" type="number" value="2011" name="year"/> ' .
  187. '<label for="month">From month (and earlier):</label> ' .
  188. '<select name="month" id="month" class="mw-month-selector">' .
  189. '<option value="-1">all</option>' . "\n" .
  190. '<option value="1">January</option>' . "\n" .
  191. '<option value="2" selected="">February</option>' . "\n" .
  192. '<option value="3">March</option>' . "\n" .
  193. '<option value="4">April</option>' . "\n" .
  194. '<option value="5">May</option>' . "\n" .
  195. '<option value="6">June</option>' . "\n" .
  196. '<option value="7">July</option>' . "\n" .
  197. '<option value="8">August</option>' . "\n" .
  198. '<option value="9">September</option>' . "\n" .
  199. '<option value="10">October</option>' . "\n" .
  200. '<option value="11">November</option>' . "\n" .
  201. '<option value="12">December</option></select>',
  202. Xml::dateMenu( 2011, 02 ),
  203. "Date menu for february 2011"
  204. );
  205. $this->assertEquals(
  206. '<label for="year">From year (and earlier):</label> ' .
  207. '<input id="year" maxlength="4" size="7" type="number" value="2011" name="year"/> ' .
  208. '<label for="month">From month (and earlier):</label> ' .
  209. '<select name="month" id="month" class="mw-month-selector">' .
  210. '<option value="-1">all</option>' . "\n" .
  211. '<option value="1">January</option>' . "\n" .
  212. '<option value="2">February</option>' . "\n" .
  213. '<option value="3">March</option>' . "\n" .
  214. '<option value="4">April</option>' . "\n" .
  215. '<option value="5">May</option>' . "\n" .
  216. '<option value="6">June</option>' . "\n" .
  217. '<option value="7">July</option>' . "\n" .
  218. '<option value="8">August</option>' . "\n" .
  219. '<option value="9">September</option>' . "\n" .
  220. '<option value="10">October</option>' . "\n" .
  221. '<option value="11">November</option>' . "\n" .
  222. '<option value="12">December</option></select>',
  223. Xml::dateMenu( 2011, -1 ),
  224. "Date menu with negative month for 'All'"
  225. );
  226. $this->assertEquals(
  227. Xml::dateMenu( $curYear, $curMonth ),
  228. Xml::dateMenu( '', $curMonth ),
  229. "Date menu year is the current one when not specified"
  230. );
  231. $wantedYear = $nextMonth == 1 ? $curYear : $prevYear;
  232. $this->assertEquals(
  233. Xml::dateMenu( $wantedYear, $nextMonth ),
  234. Xml::dateMenu( '', $nextMonth ),
  235. "Date menu next month is 11 months ago"
  236. );
  237. $this->assertEquals(
  238. '<label for="year">From year (and earlier):</label> ' .
  239. '<input id="year" maxlength="4" size="7" type="number" name="year"/> ' .
  240. '<label for="month">From month (and earlier):</label> ' .
  241. '<select name="month" id="month" class="mw-month-selector">' .
  242. '<option value="-1">all</option>' . "\n" .
  243. '<option value="1">January</option>' . "\n" .
  244. '<option value="2">February</option>' . "\n" .
  245. '<option value="3">March</option>' . "\n" .
  246. '<option value="4">April</option>' . "\n" .
  247. '<option value="5">May</option>' . "\n" .
  248. '<option value="6">June</option>' . "\n" .
  249. '<option value="7">July</option>' . "\n" .
  250. '<option value="8">August</option>' . "\n" .
  251. '<option value="9">September</option>' . "\n" .
  252. '<option value="10">October</option>' . "\n" .
  253. '<option value="11">November</option>' . "\n" .
  254. '<option value="12">December</option></select>',
  255. Xml::dateMenu( '', '' ),
  256. "Date menu with neither year or month"
  257. );
  258. }
  259. /**
  260. * @covers Xml::textarea
  261. */
  262. public function testTextareaNoContent() {
  263. $this->assertEquals(
  264. '<textarea name="name" id="name" cols="40" rows="5"></textarea>',
  265. Xml::textarea( 'name', '' ),
  266. 'textarea() with not content'
  267. );
  268. }
  269. /**
  270. * @covers Xml::textarea
  271. */
  272. public function testTextareaAttribs() {
  273. $this->assertEquals(
  274. '<textarea name="name" id="name" cols="20" rows="10">&lt;txt&gt;</textarea>',
  275. Xml::textarea( 'name', '<txt>', 20, 10 ),
  276. 'textarea() with custom attribs'
  277. );
  278. }
  279. /**
  280. * @covers Xml::label
  281. */
  282. public function testLabelCreation() {
  283. $this->assertEquals(
  284. '<label for="id">name</label>',
  285. Xml::label( 'name', 'id' ),
  286. 'label() with no attribs'
  287. );
  288. }
  289. /**
  290. * @covers Xml::label
  291. */
  292. public function testLabelAttributeCanOnlyBeClassOrTitle() {
  293. $this->assertEquals(
  294. '<label for="id">name</label>',
  295. Xml::label( 'name', 'id', [ 'generated' => true ] ),
  296. 'label() can not be given a generated attribute'
  297. );
  298. $this->assertEquals(
  299. '<label for="id" class="nice">name</label>',
  300. Xml::label( 'name', 'id', [ 'class' => 'nice' ] ),
  301. 'label() can get a class attribute'
  302. );
  303. $this->assertEquals(
  304. '<label for="id" title="nice tooltip">name</label>',
  305. Xml::label( 'name', 'id', [ 'title' => 'nice tooltip' ] ),
  306. 'label() can get a title attribute'
  307. );
  308. $this->assertEquals(
  309. '<label for="id" class="nice" title="nice tooltip">name</label>',
  310. Xml::label( 'name', 'id', [
  311. 'generated' => true,
  312. 'class' => 'nice',
  313. 'title' => 'nice tooltip',
  314. 'anotherattr' => 'value',
  315. ]
  316. ),
  317. 'label() skip all attributes but "class" and "title"'
  318. );
  319. }
  320. /**
  321. * @covers Xml::languageSelector
  322. */
  323. public function testLanguageSelector() {
  324. $select = Xml::languageSelector( 'en', true, null,
  325. [ 'id' => 'testlang' ], wfMessage( 'yourlanguage' ) );
  326. $this->assertEquals(
  327. '<label for="testlang">Language:</label>',
  328. $select[0]
  329. );
  330. }
  331. /**
  332. * @covers Xml::encodeJsVar
  333. */
  334. public function testEncodeJsVarBoolean() {
  335. $this->assertEquals(
  336. 'true',
  337. Xml::encodeJsVar( true ),
  338. 'encodeJsVar() with boolean'
  339. );
  340. }
  341. /**
  342. * @covers Xml::encodeJsVar
  343. */
  344. public function testEncodeJsVarNull() {
  345. $this->assertEquals(
  346. 'null',
  347. Xml::encodeJsVar( null ),
  348. 'encodeJsVar() with null'
  349. );
  350. }
  351. /**
  352. * @covers Xml::encodeJsVar
  353. */
  354. public function testEncodeJsVarArray() {
  355. $this->assertEquals(
  356. '["a",1]',
  357. Xml::encodeJsVar( [ 'a', 1 ] ),
  358. 'encodeJsVar() with array'
  359. );
  360. $this->assertEquals(
  361. '{"a":"a","b":1}',
  362. Xml::encodeJsVar( [ 'a' => 'a', 'b' => 1 ] ),
  363. 'encodeJsVar() with associative array'
  364. );
  365. }
  366. /**
  367. * @covers Xml::encodeJsVar
  368. */
  369. public function testEncodeJsVarObject() {
  370. $this->assertEquals(
  371. '{"a":"a","b":1}',
  372. Xml::encodeJsVar( (object)[ 'a' => 'a', 'b' => 1 ] ),
  373. 'encodeJsVar() with object'
  374. );
  375. }
  376. /**
  377. * @covers Xml::encodeJsVar
  378. */
  379. public function testEncodeJsVarInt() {
  380. $this->assertEquals(
  381. '123456',
  382. Xml::encodeJsVar( 123456 ),
  383. 'encodeJsVar() with int'
  384. );
  385. }
  386. /**
  387. * @covers Xml::encodeJsVar
  388. */
  389. public function testEncodeJsVarFloat() {
  390. $this->assertEquals(
  391. '1.23456',
  392. Xml::encodeJsVar( 1.23456 ),
  393. 'encodeJsVar() with float'
  394. );
  395. }
  396. /**
  397. * @covers Xml::encodeJsVar
  398. */
  399. public function testEncodeJsVarIntString() {
  400. $this->assertEquals(
  401. '"123456"',
  402. Xml::encodeJsVar( '123456' ),
  403. 'encodeJsVar() with int-like string'
  404. );
  405. }
  406. /**
  407. * @covers Xml::encodeJsVar
  408. */
  409. public function testEncodeJsVarFloatString() {
  410. $this->assertEquals(
  411. '"1.23456"',
  412. Xml::encodeJsVar( '1.23456' ),
  413. 'encodeJsVar() with float-like string'
  414. );
  415. }
  416. /**
  417. * @covers Xml::listDropDown
  418. */
  419. public function testListDropDown() {
  420. $this->assertEquals(
  421. '<select name="test-name" id="test-name" class="test-css" tabindex="2">' .
  422. '<option value="other">other reasons</option>' . "\n" .
  423. '<optgroup label="Foo">' .
  424. '<option value="Foo 1">Foo 1</option>' . "\n" .
  425. '<option value="Example" selected="">Example</option>' . "\n" .
  426. '</optgroup>' . "\n" .
  427. '<optgroup label="Bar">' .
  428. '<option value="Bar 1">Bar 1</option>' . "\n" .
  429. '</optgroup>' .
  430. '</select>',
  431. Xml::listDropDown(
  432. // name
  433. 'test-name',
  434. // source list
  435. "* Foo\n** Foo 1\n** Example\n* Bar\n** Bar 1",
  436. // other
  437. 'other reasons',
  438. // selected
  439. 'Example',
  440. // class
  441. 'test-css',
  442. // tabindex
  443. 2
  444. )
  445. );
  446. }
  447. /**
  448. * @covers Xml::listDropDownOptions
  449. */
  450. public function testListDropDownOptions() {
  451. $this->assertEquals(
  452. [
  453. 'other reasons' => 'other',
  454. 'Foo' => [
  455. 'Foo 1' => 'Foo 1',
  456. 'Example' => 'Example',
  457. ],
  458. 'Bar' => [
  459. 'Bar 1' => 'Bar 1',
  460. ],
  461. ],
  462. Xml::listDropDownOptions(
  463. "* Foo\n** Foo 1\n** Example\n* Bar\n** Bar 1",
  464. [ 'other' => 'other reasons' ]
  465. )
  466. );
  467. }
  468. /**
  469. * @covers Xml::listDropDownOptionsOoui
  470. */
  471. public function testListDropDownOptionsOoui() {
  472. $this->assertEquals(
  473. [
  474. [ 'data' => 'other', 'label' => 'other reasons' ],
  475. [ 'optgroup' => 'Foo' ],
  476. [ 'data' => 'Foo 1', 'label' => 'Foo 1' ],
  477. [ 'data' => 'Example', 'label' => 'Example' ],
  478. [ 'optgroup' => 'Bar' ],
  479. [ 'data' => 'Bar 1', 'label' => 'Bar 1' ],
  480. ],
  481. Xml::listDropDownOptionsOoui( [
  482. 'other reasons' => 'other',
  483. 'Foo' => [
  484. 'Foo 1' => 'Foo 1',
  485. 'Example' => 'Example',
  486. ],
  487. 'Bar' => [
  488. 'Bar 1' => 'Bar 1',
  489. ],
  490. ] )
  491. );
  492. }
  493. /**
  494. * @covers Xml::fieldset
  495. */
  496. public function testFieldset() {
  497. $this->assertEquals(
  498. "<fieldset>\n",
  499. Xml::fieldset(),
  500. 'Opening tag'
  501. );
  502. $this->assertEquals(
  503. "<fieldset>\n",
  504. Xml::fieldset( false ),
  505. 'Opening tag (false means no legend)'
  506. );
  507. $this->assertEquals(
  508. "<fieldset>\n",
  509. Xml::fieldset( '' ),
  510. 'Opening tag (empty string also means no legend)'
  511. );
  512. $this->assertEquals(
  513. "<fieldset>\n<legend>Foo</legend>\n",
  514. Xml::fieldset( 'Foo' ),
  515. 'Opening tag with legend'
  516. );
  517. $this->assertEquals(
  518. "<fieldset>\n<legend>Foo</legend>\nBar\n</fieldset>\n",
  519. Xml::fieldset( 'Foo', 'Bar' ),
  520. 'Entire element with legend'
  521. );
  522. $this->assertEquals(
  523. "<fieldset>\n<legend>Foo</legend>\n",
  524. Xml::fieldset( 'Foo', false ),
  525. 'Opening tag with legend (false means no content and no closing tag)'
  526. );
  527. $this->assertEquals(
  528. "<fieldset>\n<legend>Foo</legend>\n\n</fieldset>\n",
  529. Xml::fieldset( 'Foo', '' ),
  530. 'Entire element with legend but no content (empty string generates a closing tag)'
  531. );
  532. $this->assertEquals(
  533. "<fieldset class=\"bar\">\n<legend>Foo</legend>\nBar\n</fieldset>\n",
  534. Xml::fieldset( 'Foo', 'Bar', [ 'class' => 'bar' ] ),
  535. 'Opening tag with legend and attributes'
  536. );
  537. $this->assertEquals(
  538. "<fieldset class=\"bar\">\n<legend>Foo</legend>\n",
  539. Xml::fieldset( 'Foo', false, [ 'class' => 'bar' ] ),
  540. 'Entire element with legend and attributes'
  541. );
  542. }
  543. /**
  544. * @covers Xml::buildTable
  545. */
  546. public function testBuildTable() {
  547. $firstRow = [ 'foo', 'bar' ];
  548. $secondRow = [ 'Berlin', 'Tehran' ];
  549. $headers = [ 'header1', 'header2' ];
  550. $expected = '<table id="testTable"><thead id="testTable"><th>header1</th>' .
  551. '<th>header2</th></thead><tr><td>foo</td><td>bar</td></tr><tr><td>Berlin</td>' .
  552. '<td>Tehran</td></tr></table>';
  553. $this->assertEquals(
  554. $expected,
  555. Xml::buildTable(
  556. [ $firstRow, $secondRow ],
  557. [ 'id' => 'testTable' ],
  558. $headers
  559. )
  560. );
  561. }
  562. /**
  563. * @covers Xml::buildTableRow
  564. */
  565. public function testBuildTableRow() {
  566. $this->assertEquals(
  567. '<tr id="testRow"><td>foo</td><td>bar</td></tr>',
  568. Xml::buildTableRow( [ 'id' => 'testRow' ], [ 'foo', 'bar' ] )
  569. );
  570. }
  571. }