MessageTest.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854
  1. <?php
  2. use MediaWiki\MediaWikiServices;
  3. use Wikimedia\TestingAccessWrapper;
  4. /**
  5. * @group Database
  6. */
  7. class MessageTest extends MediaWikiLangTestCase {
  8. protected function setUp() {
  9. parent::setUp();
  10. $this->setMwGlobals( [
  11. 'wgForceUIMsgAsContentMsg' => [],
  12. ] );
  13. $this->setUserLang( 'en' );
  14. }
  15. /**
  16. * @covers Message::__construct
  17. * @dataProvider provideConstructor
  18. */
  19. public function testConstructor( $expectedLang, $key, $params, $language ) {
  20. $message = new Message( $key, $params, $language );
  21. $this->assertSame( $key, $message->getKey() );
  22. $this->assertSame( $params, $message->getParams() );
  23. $this->assertSame( $expectedLang->getCode(), $message->getLanguage()->getCode() );
  24. $messageSpecifier = $this->getMockForAbstractClass( MessageSpecifier::class );
  25. $messageSpecifier->expects( $this->any() )
  26. ->method( 'getKey' )->will( $this->returnValue( $key ) );
  27. $messageSpecifier->expects( $this->any() )
  28. ->method( 'getParams' )->will( $this->returnValue( $params ) );
  29. $message = new Message( $messageSpecifier, [], $language );
  30. $this->assertSame( $key, $message->getKey() );
  31. $this->assertSame( $params, $message->getParams() );
  32. $this->assertSame( $expectedLang->getCode(), $message->getLanguage()->getCode() );
  33. }
  34. public static function provideConstructor() {
  35. $langDe = Language::factory( 'de' );
  36. $langEn = Language::factory( 'en' );
  37. return [
  38. [ $langDe, 'foo', [], $langDe ],
  39. [ $langDe, 'foo', [ 'bar' ], $langDe ],
  40. [ $langEn, 'foo', [ 'bar' ], null ]
  41. ];
  42. }
  43. public static function provideConstructorParams() {
  44. return [
  45. [
  46. [],
  47. [],
  48. ],
  49. [
  50. [],
  51. [ [] ],
  52. ],
  53. [
  54. [ 'foo' ],
  55. [ 'foo' ],
  56. ],
  57. [
  58. [ 'foo', 'bar' ],
  59. [ 'foo', 'bar' ],
  60. ],
  61. [
  62. [ 'baz' ],
  63. [ [ 'baz' ] ],
  64. ],
  65. [
  66. [ 'baz', 'foo' ],
  67. [ [ 'baz', 'foo' ] ],
  68. ],
  69. [
  70. [ Message::rawParam( 'baz' ) ],
  71. [ Message::rawParam( 'baz' ) ],
  72. ],
  73. [
  74. [ Message::rawParam( 'baz' ), 'foo' ],
  75. [ Message::rawParam( 'baz' ), 'foo' ],
  76. ],
  77. [
  78. [ Message::rawParam( 'baz' ) ],
  79. [ [ Message::rawParam( 'baz' ) ] ],
  80. ],
  81. [
  82. [ Message::rawParam( 'baz' ), 'foo' ],
  83. [ [ Message::rawParam( 'baz' ), 'foo' ] ],
  84. ],
  85. // Test handling of erroneous input, to detect if it changes
  86. [
  87. [ [ 'baz', 'foo' ], 'hhh' ],
  88. [ [ 'baz', 'foo' ], 'hhh' ],
  89. ],
  90. [
  91. [ [ 'baz', 'foo' ], 'hhh', [ 'ahahahahha' ] ],
  92. [ [ 'baz', 'foo' ], 'hhh', [ 'ahahahahha' ] ],
  93. ],
  94. [
  95. [ [ 'baz', 'foo' ], [ 'ahahahahha' ] ],
  96. [ [ 'baz', 'foo' ], [ 'ahahahahha' ] ],
  97. ],
  98. [
  99. [ [ 'baz' ], [ 'ahahahahha' ] ],
  100. [ [ 'baz' ], [ 'ahahahahha' ] ],
  101. ],
  102. ];
  103. }
  104. /**
  105. * @covers Message::__construct
  106. * @covers Message::getParams
  107. * @dataProvider provideConstructorParams
  108. */
  109. public function testConstructorParams( $expected, $args ) {
  110. $msg = new Message( 'imasomething' );
  111. $returned = call_user_func_array( [ $msg, 'params' ], $args );
  112. $this->assertSame( $msg, $returned );
  113. $this->assertSame( $expected, $msg->getParams() );
  114. }
  115. public static function provideConstructorLanguage() {
  116. return [
  117. [ 'foo', [ 'bar' ], 'en' ],
  118. [ 'foo', [ 'bar' ], 'de' ]
  119. ];
  120. }
  121. /**
  122. * @covers Message::__construct
  123. * @covers Message::getLanguage
  124. * @dataProvider provideConstructorLanguage
  125. */
  126. public function testConstructorLanguage( $key, $params, $languageCode ) {
  127. $language = Language::factory( $languageCode );
  128. $message = new Message( $key, $params, $language );
  129. $this->assertEquals( $language, $message->getLanguage() );
  130. }
  131. public static function provideKeys() {
  132. return [
  133. 'string' => [
  134. 'key' => 'mainpage',
  135. 'expected' => [ 'mainpage' ],
  136. ],
  137. 'single' => [
  138. 'key' => [ 'mainpage' ],
  139. 'expected' => [ 'mainpage' ],
  140. ],
  141. 'multi' => [
  142. 'key' => [ 'mainpage-foo', 'mainpage-bar', 'mainpage' ],
  143. 'expected' => [ 'mainpage-foo', 'mainpage-bar', 'mainpage' ],
  144. ],
  145. 'empty' => [
  146. 'key' => [],
  147. 'expected' => null,
  148. 'exception' => 'InvalidArgumentException',
  149. ],
  150. 'null' => [
  151. 'key' => null,
  152. 'expected' => null,
  153. 'exception' => 'InvalidArgumentException',
  154. ],
  155. 'bad type' => [
  156. 'key' => 123,
  157. 'expected' => null,
  158. 'exception' => 'InvalidArgumentException',
  159. ],
  160. ];
  161. }
  162. /**
  163. * @covers Message::__construct
  164. * @covers Message::getKey
  165. * @covers Message::isMultiKey
  166. * @covers Message::getKeysToTry
  167. * @dataProvider provideKeys
  168. */
  169. public function testKeys( $key, $expected, $exception = null ) {
  170. if ( $exception ) {
  171. $this->setExpectedException( $exception );
  172. }
  173. $msg = new Message( $key );
  174. $this->assertContains( $msg->getKey(), $expected );
  175. $this->assertSame( $expected, $msg->getKeysToTry() );
  176. $this->assertSame( count( $expected ) > 1, $msg->isMultiKey() );
  177. }
  178. /**
  179. * @covers ::wfMessage
  180. */
  181. public function testWfMessage() {
  182. $this->assertInstanceOf( Message::class, wfMessage( 'mainpage' ) );
  183. $this->assertInstanceOf( Message::class, wfMessage( 'i-dont-exist-evar' ) );
  184. }
  185. /**
  186. * @covers Message::newFromKey
  187. */
  188. public function testNewFromKey() {
  189. $this->assertInstanceOf( Message::class, Message::newFromKey( 'mainpage' ) );
  190. $this->assertInstanceOf( Message::class, Message::newFromKey( 'i-dont-exist-evar' ) );
  191. }
  192. /**
  193. * @covers ::wfMessage
  194. * @covers Message::__construct
  195. */
  196. public function testWfMessageParams() {
  197. $this->assertSame( 'Return to $1.', wfMessage( 'returnto' )->text() );
  198. $this->assertSame( 'Return to $1.', wfMessage( 'returnto', [] )->text() );
  199. $this->assertSame(
  200. 'Return to 1,024.',
  201. wfMessage( 'returnto', Message::numParam( 1024 ) )->text()
  202. );
  203. $this->assertSame(
  204. 'Return to 1,024.',
  205. wfMessage( 'returnto', [ Message::numParam( 1024 ) ] )->text()
  206. );
  207. $this->assertSame(
  208. 'You have foo (bar).',
  209. wfMessage( 'youhavenewmessages', 'foo', 'bar' )->text()
  210. );
  211. $this->assertSame(
  212. 'You have foo (bar).',
  213. wfMessage( 'youhavenewmessages', [ 'foo', 'bar' ] )->text()
  214. );
  215. $this->assertSame(
  216. 'You have 1,024 (bar).',
  217. wfMessage(
  218. 'youhavenewmessages',
  219. Message::numParam( 1024 ), 'bar'
  220. )->text()
  221. );
  222. $this->assertSame(
  223. 'You have foo (2,048).',
  224. wfMessage(
  225. 'youhavenewmessages',
  226. 'foo', Message::numParam( 2048 )
  227. )->text()
  228. );
  229. $this->assertSame(
  230. 'You have 1,024 (2,048).',
  231. wfMessage(
  232. 'youhavenewmessages',
  233. [ Message::numParam( 1024 ), Message::numParam( 2048 ) ]
  234. )->text()
  235. );
  236. }
  237. /**
  238. * @covers Message::exists
  239. */
  240. public function testExists() {
  241. $this->assertTrue( wfMessage( 'mainpage' )->exists() );
  242. $this->assertTrue( wfMessage( 'mainpage' )->params( [] )->exists() );
  243. $this->assertTrue( wfMessage( 'mainpage' )->rawParams( 'foo', 123 )->exists() );
  244. $this->assertFalse( wfMessage( 'i-dont-exist-evar' )->exists() );
  245. $this->assertFalse( wfMessage( 'i-dont-exist-evar' )->params( [] )->exists() );
  246. $this->assertFalse( wfMessage( 'i-dont-exist-evar' )->rawParams( 'foo', 123 )->exists() );
  247. }
  248. /**
  249. * @covers Message::__construct
  250. * @covers Message::text
  251. * @covers Message::plain
  252. * @covers Message::escaped
  253. * @covers Message::toString
  254. */
  255. public function testToStringKey() {
  256. $this->assertSame( 'Main Page', wfMessage( 'mainpage' )->text() );
  257. $this->assertSame( '⧼i-dont-exist-evar⧽', wfMessage( 'i-dont-exist-evar' )->text() );
  258. $this->assertSame( '⧼i&lt;dont&gt;exist-evar⧽', wfMessage( 'i<dont>exist-evar' )->text() );
  259. $this->assertSame( '⧼i-dont-exist-evar⧽', wfMessage( 'i-dont-exist-evar' )->plain() );
  260. $this->assertSame( '⧼i&lt;dont&gt;exist-evar⧽', wfMessage( 'i<dont>exist-evar' )->plain() );
  261. $this->assertSame( '⧼i-dont-exist-evar⧽', wfMessage( 'i-dont-exist-evar' )->escaped() );
  262. $this->assertSame(
  263. '⧼i&lt;dont&gt;exist-evar⧽',
  264. wfMessage( 'i<dont>exist-evar' )->escaped()
  265. );
  266. }
  267. public static function provideToString() {
  268. return [
  269. // key, transformation, transformed, transformed implicitly
  270. [ 'mainpage', 'plain', 'Main Page', 'Main Page' ],
  271. [ 'i-dont-exist-evar', 'plain', '⧼i-dont-exist-evar⧽', '⧼i-dont-exist-evar⧽' ],
  272. [ 'i-dont-exist-evar', 'escaped', '⧼i-dont-exist-evar⧽', '⧼i-dont-exist-evar⧽' ],
  273. [ 'script>alert(1)</script', 'escaped', '⧼script&gt;alert(1)&lt;/script⧽',
  274. '⧼script&gt;alert(1)&lt;/script⧽' ],
  275. [ 'script>alert(1)</script', 'plain', '⧼script&gt;alert(1)&lt;/script⧽',
  276. '⧼script&gt;alert(1)&lt;/script⧽' ],
  277. ];
  278. }
  279. /**
  280. * @covers Message::toString
  281. * @covers Message::__toString
  282. * @dataProvider provideToString
  283. */
  284. public function testToString( $key, $format, $expect, $expectImplicit ) {
  285. $msg = new Message( $key );
  286. $this->assertSame( $expect, $msg->$format() );
  287. $this->assertSame( $expect, $msg->toString(), 'toString is unaffected by previous call' );
  288. $this->assertSame( $expectImplicit, $msg->__toString() );
  289. $this->assertSame( $expect, $msg->toString(), 'toString is unaffected by __toString' );
  290. }
  291. public static function provideToString_raw() {
  292. return [
  293. [ '<span>foo</span>', 'parse', '<span>foo</span>', '<span>foo</span>' ],
  294. [ '<span>foo</span>', 'escaped', '&lt;span&gt;foo&lt;/span&gt;',
  295. '<span>foo</span>' ],
  296. [ '<span>foo</span>', 'plain', '<span>foo</span>', '<span>foo</span>' ],
  297. [ '<script>alert(1)</script>', 'parse', '&lt;script&gt;alert(1)&lt;/script&gt;',
  298. '&lt;script&gt;alert(1)&lt;/script&gt;' ],
  299. [ '<script>alert(1)</script>', 'escaped', '&lt;script&gt;alert(1)&lt;/script&gt;',
  300. '&lt;script&gt;alert(1)&lt;/script&gt;' ],
  301. [ '<script>alert(1)</script>', 'plain', '<script>alert(1)</script>',
  302. '&lt;script&gt;alert(1)&lt;/script&gt;' ],
  303. ];
  304. }
  305. /**
  306. * @covers Message::toString
  307. * @covers Message::__toString
  308. * @dataProvider provideToString_raw
  309. */
  310. public function testToString_raw( $message, $format, $expect, $expectImplicit ) {
  311. // make the message behave like RawMessage and use the key as-is
  312. $msg = $this->getMockBuilder( Message::class )->setMethods( [ 'fetchMessage' ] )
  313. ->disableOriginalConstructor()
  314. ->getMock();
  315. $msg->expects( $this->any() )->method( 'fetchMessage' )->willReturn( $message );
  316. /** @var Message $msg */
  317. $this->assertSame( $expect, $msg->$format() );
  318. $this->assertSame( $expect, $msg->toString(), 'toString is unaffected by previous call' );
  319. $this->assertSame( $expectImplicit, $msg->__toString() );
  320. $this->assertSame( $expect, $msg->toString(), 'toString is unaffected by __toString' );
  321. }
  322. /**
  323. * @covers Message::inLanguage
  324. */
  325. public function testInLanguage() {
  326. $this->assertSame( 'Main Page', wfMessage( 'mainpage' )->inLanguage( 'en' )->text() );
  327. $this->assertSame( 'Заглавная страница',
  328. wfMessage( 'mainpage' )->inLanguage( 'ru' )->text() );
  329. // NOTE: make sure internal caching of the message text is reset appropriately
  330. $msg = wfMessage( 'mainpage' );
  331. $this->assertSame( 'Main Page', $msg->inLanguage( Language::factory( 'en' ) )->text() );
  332. $this->assertSame(
  333. 'Заглавная страница',
  334. $msg->inLanguage( Language::factory( 'ru' ) )->text()
  335. );
  336. }
  337. /**
  338. * @covers Message::rawParam
  339. * @covers Message::rawParams
  340. */
  341. public function testRawParams() {
  342. $this->assertSame(
  343. '(Заглавная страница)',
  344. wfMessage( 'parentheses', 'Заглавная страница' )->plain()
  345. );
  346. $this->assertSame(
  347. '(Заглавная страница $1)',
  348. wfMessage( 'parentheses', 'Заглавная страница $1' )->plain()
  349. );
  350. $this->assertSame(
  351. '(Заглавная страница)',
  352. wfMessage( 'parentheses' )->rawParams( 'Заглавная страница' )->plain()
  353. );
  354. $this->assertSame(
  355. '(Заглавная страница $1)',
  356. wfMessage( 'parentheses' )->rawParams( 'Заглавная страница $1' )->plain()
  357. );
  358. }
  359. /**
  360. * @covers RawMessage::__construct
  361. * @covers RawMessage::fetchMessage
  362. */
  363. public function testRawMessage() {
  364. $msg = new RawMessage( 'example &' );
  365. $this->assertSame( 'example &', $msg->plain() );
  366. $this->assertSame( 'example &amp;', $msg->escaped() );
  367. }
  368. public function testRawHtmlInMsg() {
  369. $this->setMwGlobals( 'wgRawHtml', true );
  370. // We have to reset the core hook registration.
  371. // to register the html hook
  372. MessageCache::destroyInstance();
  373. $this->setMwGlobals( 'wgParser',
  374. MediaWikiServices::getInstance()->getParserFactory()->create() );
  375. $msg = new RawMessage( '<html><script>alert("xss")</script></html>' );
  376. $txt = '<span class="error">&lt;html&gt; tags cannot be' .
  377. ' used outside of normal pages.</span>';
  378. $this->assertSame( $txt, $msg->parse() );
  379. }
  380. /**
  381. * @covers Message::params
  382. * @covers Message::toString
  383. * @covers Message::replaceParameters
  384. */
  385. public function testReplaceManyParams() {
  386. $msg = new RawMessage( '$1$2$3$4$5$6$7$8$9$10$11$12' );
  387. // One less than above has placeholders
  388. $params = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k' ];
  389. $this->assertSame(
  390. 'abcdefghijka2',
  391. $msg->params( $params )->plain(),
  392. 'Params > 9 are replaced correctly'
  393. );
  394. $msg = new RawMessage( 'Params$*' );
  395. $params = [ 'ab', 'bc', 'cd' ];
  396. $this->assertSame(
  397. 'Params: ab, bc, cd',
  398. $msg->params( $params )->text()
  399. );
  400. }
  401. /**
  402. * @covers Message::numParam
  403. * @covers Message::numParams
  404. */
  405. public function testNumParams() {
  406. $lang = Language::factory( 'en' );
  407. $msg = new RawMessage( '$1' );
  408. $this->assertSame(
  409. $lang->formatNum( 123456.789 ),
  410. $msg->inLanguage( $lang )->numParams( 123456.789 )->plain(),
  411. 'numParams is handled correctly'
  412. );
  413. }
  414. /**
  415. * @covers Message::durationParam
  416. * @covers Message::durationParams
  417. */
  418. public function testDurationParams() {
  419. $lang = Language::factory( 'en' );
  420. $msg = new RawMessage( '$1' );
  421. $this->assertSame(
  422. $lang->formatDuration( 1234 ),
  423. $msg->inLanguage( $lang )->durationParams( 1234 )->plain(),
  424. 'durationParams is handled correctly'
  425. );
  426. }
  427. /**
  428. * FIXME: This should not need database, but Language#formatExpiry does (T57912)
  429. * @covers Message::expiryParam
  430. * @covers Message::expiryParams
  431. */
  432. public function testExpiryParams() {
  433. $lang = Language::factory( 'en' );
  434. $msg = new RawMessage( '$1' );
  435. $this->assertSame(
  436. $lang->formatExpiry( wfTimestampNow() ),
  437. $msg->inLanguage( $lang )->expiryParams( wfTimestampNow() )->plain(),
  438. 'expiryParams is handled correctly'
  439. );
  440. }
  441. /**
  442. * @covers Message::timeperiodParam
  443. * @covers Message::timeperiodParams
  444. */
  445. public function testTimeperiodParams() {
  446. $lang = Language::factory( 'en' );
  447. $msg = new RawMessage( '$1' );
  448. $this->assertSame(
  449. $lang->formatTimePeriod( 1234 ),
  450. $msg->inLanguage( $lang )->timeperiodParams( 1234 )->plain(),
  451. 'timeperiodParams is handled correctly'
  452. );
  453. }
  454. /**
  455. * @covers Message::sizeParam
  456. * @covers Message::sizeParams
  457. */
  458. public function testSizeParams() {
  459. $lang = Language::factory( 'en' );
  460. $msg = new RawMessage( '$1' );
  461. $this->assertSame(
  462. $lang->formatSize( 123456 ),
  463. $msg->inLanguage( $lang )->sizeParams( 123456 )->plain(),
  464. 'sizeParams is handled correctly'
  465. );
  466. }
  467. /**
  468. * @covers Message::bitrateParam
  469. * @covers Message::bitrateParams
  470. */
  471. public function testBitrateParams() {
  472. $lang = Language::factory( 'en' );
  473. $msg = new RawMessage( '$1' );
  474. $this->assertSame(
  475. $lang->formatBitrate( 123456 ),
  476. $msg->inLanguage( $lang )->bitrateParams( 123456 )->plain(),
  477. 'bitrateParams is handled correctly'
  478. );
  479. }
  480. public static function providePlaintextParams() {
  481. return [
  482. [
  483. 'one $2 <div>foo</div> [[Bar]] {{Baz}} &lt;',
  484. 'plain',
  485. ],
  486. [
  487. // expect
  488. 'one $2 <div>foo</div> [[Bar]] {{Baz}} &lt;',
  489. // format
  490. 'text',
  491. ],
  492. [
  493. 'one $2 &lt;div&gt;foo&lt;/div&gt; [[Bar]] {{Baz}} &amp;lt;',
  494. 'escaped',
  495. ],
  496. [
  497. 'one $2 &lt;div&gt;foo&lt;/div&gt; [[Bar]] {{Baz}} &amp;lt;',
  498. 'parse',
  499. ],
  500. [
  501. "<p>one $2 &lt;div&gt;foo&lt;/div&gt; [[Bar]] {{Baz}} &amp;lt;\n</p>",
  502. 'parseAsBlock',
  503. ],
  504. ];
  505. }
  506. /**
  507. * @covers Message::plaintextParam
  508. * @covers Message::plaintextParams
  509. * @covers Message::formatPlaintext
  510. * @covers Message::toString
  511. * @covers Message::parse
  512. * @covers Message::parseAsBlock
  513. * @dataProvider providePlaintextParams
  514. */
  515. public function testPlaintextParams( $expect, $format ) {
  516. $lang = Language::factory( 'en' );
  517. $msg = new RawMessage( '$1 $2' );
  518. $params = [
  519. 'one $2',
  520. '<div>foo</div> [[Bar]] {{Baz}} &lt;',
  521. ];
  522. $this->assertSame(
  523. $expect,
  524. $msg->inLanguage( $lang )->plaintextParams( $params )->$format(),
  525. "Fail formatting for $format"
  526. );
  527. }
  528. public static function provideListParam() {
  529. $lang = Language::factory( 'de' );
  530. $msg1 = new Message( 'mainpage', [], $lang );
  531. $msg2 = new RawMessage( "''link''", [], $lang );
  532. return [
  533. 'Simple comma list' => [
  534. [ 'a', 'b', 'c' ],
  535. 'comma',
  536. 'text',
  537. 'a, b, c'
  538. ],
  539. 'Simple semicolon list' => [
  540. [ 'a', 'b', 'c' ],
  541. 'semicolon',
  542. 'text',
  543. 'a; b; c'
  544. ],
  545. 'Simple pipe list' => [
  546. [ 'a', 'b', 'c' ],
  547. 'pipe',
  548. 'text',
  549. 'a | b | c'
  550. ],
  551. 'Simple text list' => [
  552. [ 'a', 'b', 'c' ],
  553. 'text',
  554. 'text',
  555. 'a, b and c'
  556. ],
  557. 'Empty list' => [
  558. [],
  559. 'comma',
  560. 'text',
  561. ''
  562. ],
  563. 'List with all "before" params, ->text()' => [
  564. [ "''link''", Message::numParam( 12345678 ) ],
  565. 'semicolon',
  566. 'text',
  567. '\'\'link\'\'; 12,345,678'
  568. ],
  569. 'List with all "before" params, ->parse()' => [
  570. [ "''link''", Message::numParam( 12345678 ) ],
  571. 'semicolon',
  572. 'parse',
  573. '<i>link</i>; 12,345,678'
  574. ],
  575. 'List with all "after" params, ->text()' => [
  576. [ $msg1, $msg2, Message::rawParam( '[[foo]]' ) ],
  577. 'semicolon',
  578. 'text',
  579. 'Main Page; \'\'link\'\'; [[foo]]'
  580. ],
  581. 'List with all "after" params, ->parse()' => [
  582. [ $msg1, $msg2, Message::rawParam( '[[foo]]' ) ],
  583. 'semicolon',
  584. 'parse',
  585. 'Main Page; <i>link</i>; [[foo]]'
  586. ],
  587. 'List with both "before" and "after" params, ->text()' => [
  588. [ $msg1, $msg2, Message::rawParam( '[[foo]]' ), "''link''", Message::numParam( 12345678 ) ],
  589. 'semicolon',
  590. 'text',
  591. 'Main Page; \'\'link\'\'; [[foo]]; \'\'link\'\'; 12,345,678'
  592. ],
  593. 'List with both "before" and "after" params, ->parse()' => [
  594. [ $msg1, $msg2, Message::rawParam( '[[foo]]' ), "''link''", Message::numParam( 12345678 ) ],
  595. 'semicolon',
  596. 'parse',
  597. 'Main Page; <i>link</i>; [[foo]]; <i>link</i>; 12,345,678'
  598. ],
  599. ];
  600. }
  601. /**
  602. * @covers Message::listParam
  603. * @covers Message::extractParam
  604. * @covers Message::formatListParam
  605. * @dataProvider provideListParam
  606. */
  607. public function testListParam( $list, $type, $format, $expect ) {
  608. $lang = Language::factory( 'en' );
  609. $msg = new RawMessage( '$1' );
  610. $msg->params( [ Message::listParam( $list, $type ) ] );
  611. $this->assertEquals(
  612. $expect,
  613. $msg->inLanguage( $lang )->$format()
  614. );
  615. }
  616. /**
  617. * @covers Message::extractParam
  618. */
  619. public function testMessageAsParam() {
  620. $this->setMwGlobals( [
  621. 'wgScript' => '/wiki/index.php',
  622. 'wgArticlePath' => '/wiki/$1',
  623. ] );
  624. $msg = new Message( 'returnto', [
  625. new Message( 'apihelp-link', [
  626. 'foo', new Message( 'mainpage', [], Language::factory( 'en' ) )
  627. ], Language::factory( 'de' ) )
  628. ], Language::factory( 'es' ) );
  629. $this->assertEquals(
  630. 'Volver a [[Special:ApiHelp/foo|Página principal]].',
  631. $msg->text(),
  632. 'Process with ->text()'
  633. );
  634. $this->assertEquals(
  635. '<p>Volver a <a href="/wiki/Special:ApiHelp/foo" title="Special:ApiHelp/foo">Página '
  636. . "principal</a>.\n</p>",
  637. $msg->parseAsBlock(),
  638. 'Process with ->parseAsBlock()'
  639. );
  640. }
  641. public static function provideParser() {
  642. return [
  643. [
  644. "''&'' <x><!-- x -->",
  645. 'plain',
  646. ],
  647. [
  648. "''&'' <x><!-- x -->",
  649. 'text',
  650. ],
  651. [
  652. '<i>&amp;</i> &lt;x&gt;',
  653. 'parse',
  654. ],
  655. [
  656. "<p><i>&amp;</i> &lt;x&gt;\n</p>",
  657. 'parseAsBlock',
  658. ],
  659. ];
  660. }
  661. /**
  662. * @covers Message::text
  663. * @covers Message::parse
  664. * @covers Message::parseAsBlock
  665. * @covers Message::toString
  666. * @covers Message::transformText
  667. * @covers Message::parseText
  668. * @dataProvider provideParser
  669. */
  670. public function testParser( $expect, $format ) {
  671. $msg = new RawMessage( "''&'' <x><!-- x -->" );
  672. $this->assertSame(
  673. $expect,
  674. $msg->inLanguage( 'en' )->$format()
  675. );
  676. }
  677. /**
  678. * @covers Message::inContentLanguage
  679. */
  680. public function testInContentLanguage() {
  681. $this->setUserLang( 'fr' );
  682. // NOTE: make sure internal caching of the message text is reset appropriately
  683. $msg = wfMessage( 'mainpage' );
  684. $this->assertSame( 'Hauptseite', $msg->inLanguage( 'de' )->plain(), "inLanguage( 'de' )" );
  685. $this->assertSame( 'Main Page', $msg->inContentLanguage()->plain(), "inContentLanguage()" );
  686. $this->assertSame( 'Accueil', $msg->inLanguage( 'fr' )->plain(), "inLanguage( 'fr' )" );
  687. }
  688. /**
  689. * @covers Message::inContentLanguage
  690. */
  691. public function testInContentLanguageOverride() {
  692. $this->setMwGlobals( [
  693. 'wgForceUIMsgAsContentMsg' => [ 'mainpage' ],
  694. ] );
  695. $this->setUserLang( 'fr' );
  696. // NOTE: make sure internal caching of the message text is reset appropriately.
  697. // NOTE: wgForceUIMsgAsContentMsg forces the messages *current* language to be used.
  698. $msg = wfMessage( 'mainpage' );
  699. $this->assertSame(
  700. 'Accueil',
  701. $msg->inContentLanguage()->plain(),
  702. 'inContentLanguage() with ForceUIMsg override enabled'
  703. );
  704. $this->assertSame( 'Main Page', $msg->inLanguage( 'en' )->plain(), "inLanguage( 'en' )" );
  705. $this->assertSame(
  706. 'Main Page',
  707. $msg->inContentLanguage()->plain(),
  708. 'inContentLanguage() with ForceUIMsg override enabled'
  709. );
  710. $this->assertSame( 'Hauptseite', $msg->inLanguage( 'de' )->plain(), "inLanguage( 'de' )" );
  711. }
  712. /**
  713. * @expectedException MWException
  714. * @covers Message::inLanguage
  715. */
  716. public function testInLanguageThrows() {
  717. wfMessage( 'foo' )->inLanguage( 123 );
  718. }
  719. /**
  720. * @covers Message::serialize
  721. * @covers Message::unserialize
  722. */
  723. public function testSerialization() {
  724. $msg = new Message( 'parentheses' );
  725. $msg->rawParams( '<a>foo</a>' );
  726. $msg->title( Title::newFromText( 'Testing' ) );
  727. $this->assertSame( '(<a>foo</a>)', $msg->parse(), 'Sanity check' );
  728. $msg = unserialize( serialize( $msg ) );
  729. $this->assertSame( '(<a>foo</a>)', $msg->parse() );
  730. $title = TestingAccessWrapper::newFromObject( $msg )->title;
  731. $this->assertInstanceOf( Title::class, $title );
  732. $this->assertSame( 'Testing', $title->getFullText() );
  733. $msg = new Message( 'mainpage' );
  734. $msg->inLanguage( 'de' );
  735. $this->assertSame( 'Hauptseite', $msg->plain(), 'Sanity check' );
  736. $msg = unserialize( serialize( $msg ) );
  737. $this->assertSame( 'Hauptseite', $msg->plain() );
  738. }
  739. /**
  740. * @covers Message::newFromSpecifier
  741. * @dataProvider provideNewFromSpecifier
  742. */
  743. public function testNewFromSpecifier( $value, $expectedText ) {
  744. $message = Message::newFromSpecifier( $value );
  745. $this->assertInstanceOf( Message::class, $message );
  746. if ( $value instanceof Message ) {
  747. $this->assertInstanceOf( get_class( $value ), $message );
  748. $this->assertEquals( $value, $message );
  749. }
  750. $this->assertSame( $expectedText, $message->text() );
  751. }
  752. public function provideNewFromSpecifier() {
  753. $messageSpecifier = $this->getMockForAbstractClass( MessageSpecifier::class );
  754. $messageSpecifier->expects( $this->any() )->method( 'getKey' )->willReturn( 'mainpage' );
  755. $messageSpecifier->expects( $this->any() )->method( 'getParams' )->willReturn( [] );
  756. return [
  757. 'string' => [ 'mainpage', 'Main Page' ],
  758. 'array' => [ [ 'youhavenewmessages', 'foo', 'bar' ], 'You have foo (bar).' ],
  759. 'Message' => [ new Message( 'youhavenewmessages', [ 'foo', 'bar' ] ), 'You have foo (bar).' ],
  760. 'RawMessage' => [ new RawMessage( 'foo ($1)', [ 'bar' ] ), 'foo (bar)' ],
  761. 'ApiMessage' => [ new ApiMessage( [ 'mainpage' ], 'code', [ 'data' ] ), 'Main Page' ],
  762. 'MessageSpecifier' => [ $messageSpecifier, 'Main Page' ],
  763. 'nested RawMessage' => [ [ new RawMessage( 'foo ($1)', [ 'bar' ] ) ], 'foo (bar)' ],
  764. ];
  765. }
  766. }