Update Codex from v1.20.0 to v1.20.1
[mediawiki.git] / tests / phpunit / includes / language / MessageTest.php
blobb8cea329dd7476993b7ffc05cd95063cce2898ae
1 <?php
3 use MediaWiki\Api\ApiMessage;
4 use MediaWiki\Language\RawMessage;
5 use MediaWiki\MainConfigNames;
6 use MediaWiki\MediaWikiServices;
7 use MediaWiki\Message\Message;
8 use MediaWiki\Page\PageReferenceValue;
9 use MediaWiki\Title\Title;
10 use Wikimedia\Assert\ParameterTypeException;
11 use Wikimedia\Bcp47Code\Bcp47CodeValue;
12 use Wikimedia\Message\MessageSpecifier;
14 /**
15 * @group Language
16 * @group Database
17 * @covers ::wfMessage
18 * @covers \MediaWiki\Message\Message
20 class MessageTest extends MediaWikiLangTestCase {
22 protected function setUp(): void {
23 parent::setUp();
25 $this->overrideConfigValue( MainConfigNames::ForceUIMsgAsContentMsg, [] );
26 $this->setUserLang( 'en' );
29 /**
30 * @dataProvider provideConstructor
32 public function testConstructor( $expectedLang, $key, $params, $language ) {
33 $message = new Message( $key, $params, $language );
35 $this->assertSame( $key, $message->getKey() );
36 $this->assertSame( $params, $message->getParams() );
37 $this->assertSame( $expectedLang->getCode(), $message->getLanguage()->getCode() );
39 $messageSpecifier = $this->getMockForAbstractClass( MessageSpecifier::class );
40 $messageSpecifier->method( 'getKey' )->willReturn( $key );
41 $messageSpecifier->method( 'getParams' )->willReturn( $params );
42 $message = new Message( $messageSpecifier, [], $language );
44 $this->assertSame( $key, $message->getKey() );
45 $this->assertSame( $params, $message->getParams() );
46 $this->assertSame( $expectedLang->getCode(), $message->getLanguage()->getCode() );
49 public static function provideConstructor() {
50 $langDe = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'de' );
51 $langEn = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'en' );
53 return [
54 [ $langDe, 'foo', [], $langDe ],
55 [ $langDe, 'foo', [ 'bar' ], $langDe ],
56 [ $langEn, 'foo', [ 'bar' ], null ]
60 public static function provideConstructorParams() {
61 return [
63 [],
64 [],
67 [],
68 [ [] ],
71 [ 'foo' ],
72 [ 'foo' ],
75 [ 'foo', 'bar' ],
76 [ 'foo', 'bar' ],
79 [ 'baz' ],
80 [ [ 'baz' ] ],
83 [ 'baz', 'foo' ],
84 [ [ 'baz', 'foo' ] ],
87 [ Message::rawParam( 'baz' ) ],
88 [ Message::rawParam( 'baz' ) ],
91 [ Message::rawParam( 'baz' ), 'foo' ],
92 [ Message::rawParam( 'baz' ), 'foo' ],
95 [ Message::rawParam( 'baz' ) ],
96 [ [ Message::rawParam( 'baz' ) ] ],
99 [ Message::rawParam( 'baz' ), 'foo' ],
100 [ [ Message::rawParam( 'baz' ), 'foo' ] ],
103 // Test handling of erroneous input, to detect if it changes
105 [ [ 'baz', 'foo' ], 'hhh' ],
106 [ [ 'baz', 'foo' ], 'hhh' ],
109 [ [ 'baz', 'foo' ], 'hhh', [ 'ahahahahha' ] ],
110 [ [ 'baz', 'foo' ], 'hhh', [ 'ahahahahha' ] ],
113 [ [ 'baz', 'foo' ], [ 'ahahahahha' ] ],
114 [ [ 'baz', 'foo' ], [ 'ahahahahha' ] ],
117 [ [ 'baz' ], [ 'ahahahahha' ] ],
118 [ [ 'baz' ], [ 'ahahahahha' ] ],
124 * @dataProvider provideConstructorParams
126 public function testConstructorParams( $expected, $args ) {
127 $msg = new Message( 'imasomething' );
129 $returned = $msg->params( ...$args );
131 $this->assertSame( $msg, $returned );
132 $this->assertEquals( $expected, $msg->getParams() );
135 public static function provideConstructorLanguage() {
136 return [
137 [ 'foo', [ 'bar' ], 'en' ],
138 [ 'foo', [ 'bar' ], 'de' ]
143 * @dataProvider provideConstructorLanguage
145 public function testConstructorLanguage( $key, $params, $languageCode ) {
146 $language = $this->getServiceContainer()->getLanguageFactory()
147 ->getLanguage( $languageCode );
148 $message = new Message( $key, $params, $language );
150 $this->assertEquals( $language, $message->getLanguage() );
153 public static function provideKeys() {
154 return [
155 'string' => [
156 'key' => 'mainpage',
157 'expected' => [ 'mainpage' ],
159 'single' => [
160 'key' => [ 'mainpage' ],
161 'expected' => [ 'mainpage' ],
163 'multi' => [
164 'key' => [ 'mainpage-foo', 'mainpage-bar', 'mainpage' ],
165 'expected' => [ 'mainpage-foo', 'mainpage-bar', 'mainpage' ],
167 'empty' => [
168 'key' => [],
169 'expected' => null,
170 'exception' => InvalidArgumentException::class,
172 'null' => [
173 'key' => null,
174 'expected' => null,
175 'exception' => InvalidArgumentException::class,
177 'bad type' => [
178 'key' => 123,
179 'expected' => null,
180 'exception' => InvalidArgumentException::class,
186 * @dataProvider provideKeys
188 public function testKeys( $key, $expected, $exception = null ) {
189 if ( $exception ) {
190 $this->expectException( $exception );
193 $msg = new Message( $key );
194 $this->assertContains( $msg->getKey(), $expected );
195 $this->assertSame( $expected, $msg->getKeysToTry() );
196 $this->assertSame( count( $expected ) > 1, $msg->isMultiKey() );
199 public function testWfMessage() {
200 $this->assertInstanceOf( Message::class, wfMessage( 'mainpage' ) );
201 $this->assertInstanceOf( Message::class, wfMessage( 'i-dont-exist-evar' ) );
204 public function testNewFromKey() {
205 $this->assertInstanceOf( Message::class, Message::newFromKey( 'mainpage' ) );
206 $this->assertInstanceOf( Message::class, Message::newFromKey( 'i-dont-exist-evar' ) );
209 public function testWfMessageParams() {
210 $this->assertSame( 'Return to $1.', wfMessage( 'returnto' )->text() );
211 $this->assertSame( 'Return to $1.', wfMessage( 'returnto', [] )->text() );
212 $this->assertSame(
213 'Return to 1,024.',
214 wfMessage( 'returnto', Message::numParam( 1024 ) )->text()
216 $this->assertSame(
217 'Return to 1,024.',
218 wfMessage( 'returnto', [ Message::numParam( 1024 ) ] )->text()
220 $this->assertSame(
221 'You have foo (bar).',
222 wfMessage( 'new-messages', 'foo', 'bar' )->text()
224 $this->assertSame(
225 'You have foo (bar).',
226 wfMessage( 'new-messages', [ 'foo', 'bar' ] )->text()
228 $this->assertSame(
229 'You have 1,024 (bar).',
230 wfMessage(
231 'new-messages',
232 Message::numParam( 1024 ), 'bar'
233 )->text()
235 $this->assertSame(
236 'You have foo (2,048).',
237 wfMessage(
238 'new-messages',
239 'foo', Message::numParam( 2048 )
240 )->text()
242 $this->assertSame(
243 'You have 1,024 (2,048).',
244 wfMessage(
245 'new-messages',
246 [ Message::numParam( 1024 ), Message::numParam( 2048 ) ]
247 )->text()
251 public function testExists() {
252 $this->assertTrue( wfMessage( 'mainpage' )->exists() );
253 $this->assertTrue( wfMessage( 'mainpage' )->params( [] )->exists() );
254 $this->assertTrue( wfMessage( 'mainpage' )->rawParams( 'foo', 123 )->exists() );
255 $this->assertFalse( wfMessage( 'i-dont-exist-evar' )->exists() );
256 $this->assertFalse( wfMessage( 'i-dont-exist-evar' )->params( [] )->exists() );
257 $this->assertFalse( wfMessage( 'i-dont-exist-evar' )->rawParams( 'foo', 123 )->exists() );
260 public function testToStringKey() {
261 $this->assertSame( 'Main Page', wfMessage( 'mainpage' )->text() );
262 $this->assertSame( '⧼i-dont-exist-evar⧽', wfMessage( 'i-dont-exist-evar' )->text() );
263 $this->assertSame( '⧼i&lt;dont&gt;exist-evar⧽', wfMessage( 'i<dont>exist-evar' )->text() );
264 $this->assertSame( '⧼i-dont-exist-evar⧽', wfMessage( 'i-dont-exist-evar' )->plain() );
265 $this->assertSame( '⧼i&lt;dont&gt;exist-evar⧽', wfMessage( 'i<dont>exist-evar' )->plain() );
266 $this->assertSame( '⧼i-dont-exist-evar⧽', wfMessage( 'i-dont-exist-evar' )->escaped() );
267 $this->assertSame(
268 '⧼i&lt;dont&gt;exist-evar⧽',
269 wfMessage( 'i<dont>exist-evar' )->escaped()
273 public static function provideToString() {
274 return [
275 // key, transformation, transformed, transformed implicitly
276 [ 'mainpage', 'plain', 'Main Page', 'Main Page' ],
277 [ 'i-dont-exist-evar', 'plain', '⧼i-dont-exist-evar⧽', '⧼i-dont-exist-evar⧽' ],
278 [ 'i-dont-exist-evar', 'escaped', '⧼i-dont-exist-evar⧽', '⧼i-dont-exist-evar⧽' ],
279 [ 'script>alert(1)</script', 'escaped', '⧼script&gt;alert(1)&lt;/script⧽',
280 '⧼script&gt;alert(1)&lt;/script⧽' ],
281 [ 'script>alert(1)</script', 'plain', '⧼script&gt;alert(1)&lt;/script⧽',
282 '⧼script&gt;alert(1)&lt;/script⧽' ],
287 * @dataProvider provideToString
289 public function testToString( $key, $format, $expect, $expectImplicit ) {
290 $msg = new Message( $key );
291 $this->assertSame( $expect, $msg->$format() );
293 // This used to behave the same as toString() and was a security risk.
294 // It now has a stable return value that is always parsed/sanitized. (T146416)
295 $this->assertSame( $expectImplicit, $msg->__toString(), '__toString is not affected by format call' );
298 public static function provideToString_raw() {
299 return [
300 [ '<span>foo</span>', 'parse', '<span>foo</span>', '<span>foo</span>' ],
301 [ '<span>foo</span>', 'escaped', '&lt;span&gt;foo&lt;/span&gt;',
302 '<span>foo</span>' ],
303 [ '<span>foo</span>', 'plain', '<span>foo</span>', '<span>foo</span>' ],
304 [ '<script>alert(1)</script>', 'parse', '&lt;script&gt;alert(1)&lt;/script&gt;',
305 '&lt;script&gt;alert(1)&lt;/script&gt;' ],
306 [ '<script>alert(1)</script>', 'escaped', '&lt;script&gt;alert(1)&lt;/script&gt;',
307 '&lt;script&gt;alert(1)&lt;/script&gt;' ],
308 [ '<script>alert(1)</script>', 'plain', '<script>alert(1)</script>',
309 '&lt;script&gt;alert(1)&lt;/script&gt;' ],
314 * @dataProvider provideToString_raw
316 public function testToString_raw( $message, $format, $expect, $expectImplicit ) {
317 // make the message behave like RawMessage and use the key as-is
318 $msg = $this->getMockBuilder( Message::class )->onlyMethods( [ 'fetchMessage' ] )
319 ->disableOriginalConstructor()
320 ->getMock();
321 $msg->method( 'fetchMessage' )->willReturn( $message );
322 /** @var Message $msg */
324 $this->assertSame( $expect, $msg->$format() );
326 $this->assertSame( $expectImplicit, $msg->__toString() );
329 public function testInLanguage() {
330 $this->assertSame( 'Main Page', wfMessage( 'mainpage' )->inLanguage( 'en' )->text() );
331 $this->assertSame( 'Главна страна',
332 wfMessage( 'mainpage' )->inLanguage( 'sr-ec' )->text() );
334 // NOTE: make sure internal caching of the message text is reset appropriately
335 $msg = wfMessage( 'mainpage' );
336 $this->assertSame( 'Main Page', $msg->inLanguage( 'en' )->text() );
337 $this->assertSame(
338 'Главна страна',
339 $msg->inLanguage( 'sr-ec' )->text()
343 public function testInLanguageBcp47() {
344 $en = new Bcp47CodeValue( 'en' );
345 $sr = new Bcp47CodeValue( 'sr-Cyrl' );
346 $this->assertSame( 'Main Page', wfMessage( 'mainpage' )->inLanguage( $en )->text() );
347 $this->assertSame( 'Главна страна',
348 wfMessage( 'mainpage' )->inLanguage( $sr )->text() );
350 // NOTE: make sure internal caching of the message text is reset appropriately
351 $msg = wfMessage( 'mainpage' );
352 $this->assertSame( 'Main Page', $msg->inLanguage( $en )->text() );
353 $this->assertSame(
354 'Главна страна',
355 $msg->inLanguage( $sr )->text()
359 public function testRawParams() {
360 $this->assertSame(
361 '(Заглавная страница)',
362 wfMessage( 'parentheses', 'Заглавная страница' )->plain()
364 $this->assertSame(
365 '(Заглавная страница $1)',
366 wfMessage( 'parentheses', 'Заглавная страница $1' )->plain()
368 $this->assertSame(
369 '(Заглавная страница)',
370 wfMessage( 'parentheses' )->rawParams( 'Заглавная страница' )->plain()
372 $this->assertSame(
373 '(Заглавная страница $1)',
374 wfMessage( 'parentheses' )->rawParams( 'Заглавная страница $1' )->plain()
379 * @covers \MediaWiki\Language\RawMessage
381 public function testRawMessage() {
382 $msg = new RawMessage( 'example &' );
383 $this->assertSame( 'example &', $msg->plain() );
384 $this->assertSame( 'example &amp;', $msg->escaped() );
387 public static function provideRawMessage() {
388 yield 'No params' => [
389 new RawMessage( 'Foo Bar' ),
390 'Foo Bar',
392 yield 'Single param' => [
393 new RawMessage( '$1', [ 'Foo Bar' ] ),
394 'Foo Bar',
396 yield 'Multiple params' => [
397 new RawMessage( '$2 and $1', [ 'One', 'Two' ] ),
398 'Two and One',
403 * @dataProvider provideRawMessage
404 * @covers \MediaWiki\Language\RawMessage
406 public function testRawMessageParams( RawMessage $m, string $param ) {
407 $this->assertEquals( [ $param ], $m->getParams() );
411 * @dataProvider provideRawMessage
412 * @covers \MediaWiki\Language\RawMessage
414 public function testRawMessageDisassembleSpecifier( RawMessage $m, string $text ) {
415 // Check this just in case, although it's not really covered by this test.
416 $this->assertEquals( $text, $m->text(), 'output from RawMessage itself' );
417 // Verify that RawMessage can be used as a MessageSpecifier, producing the same output.
418 $msg = wfMessage( $m );
419 $this->assertEquals( $text, $msg->text(), 'output from RawMessage used as MessageSpecifier' );
420 // Verify that if you disassemble it using MessageSpecifier's getKey() and getParams() methods,
421 // then assemble a new MessageSpecifier using the return values, you will get the same output.
422 $msg2 = wfMessage( $m->getKey(), ...$m->getParams() );
423 $this->assertEquals( $text, $msg2->text(), 'output from RawMessage disassembled' );
427 * @covers \MediaWiki\Language\RawMessage
428 * @covers \MediaWiki\Parser\CoreTagHooks::html
430 public function testRawHtmlInMsg() {
431 $this->overrideConfigValue( MainConfigNames::RawHtml, true );
433 $msg = new RawMessage( '<html><script>alert("xss")</script></html>' );
434 $txt = '<span class="error">&lt;html&gt; tags cannot be' .
435 ' used outside of normal pages.</span>';
436 $this->assertSame( $txt, $msg->parse() );
439 public function testReplaceManyParams() {
440 $msg = new RawMessage( '$1$2$3$4$5$6$7$8$9$10$11$12' );
441 // One less than above has placeholders
442 $params = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k' ];
443 $this->assertSame(
444 'abcdefghijka2',
445 $msg->params( $params )->plain(),
446 'Params > 9 are replaced correctly'
450 public function testNumParams() {
451 $lang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'en' );
452 $msg = new RawMessage( '$1' );
454 $this->assertSame(
455 $lang->formatNum( 123456.789 ),
456 $msg->inLanguage( $lang )->numParams( 123456.789 )->plain(),
457 'numParams is handled correctly'
461 public function testDurationParams() {
462 $lang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'en' );
463 $msg = new RawMessage( '$1' );
465 $this->assertSame(
466 $lang->formatDuration( 1234 ),
467 $msg->inLanguage( $lang )->durationParams( 1234 )->plain(),
468 'durationParams is handled correctly'
473 * FIXME: This should not need database, but Language#formatExpiry does (T57912)
475 public function testExpiryParams() {
476 $lang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'en' );
477 $msg = new RawMessage( '$1' );
479 $ts = wfTimestampNow();
480 $this->assertSame(
481 $lang->formatExpiry( $ts ),
482 $msg->inLanguage( $lang )->expiryParams( $ts )->plain(),
483 'expiryParams is handled correctly'
487 public function testDateTimeParams() {
488 $lang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'en' );
489 $msg = new RawMessage( '$1' );
491 $ts = wfTimestampNow();
492 $this->assertSame(
493 $lang->timeanddate( $ts ),
494 $msg->inLanguage( $lang )->dateTimeParams( $ts )->plain(),
495 'dateTime is handled correctly'
499 public function testDateParams() {
500 $lang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'en' );
501 $msg = new RawMessage( '$1' );
503 $ts = wfTimestampNow();
504 $this->assertSame(
505 $lang->date( $ts ),
506 $msg->inLanguage( $lang )->dateParams( $ts )->plain(),
507 'date is handled correctly'
511 public function testTimeParams() {
512 $lang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'en' );
513 $msg = new RawMessage( '$1' );
515 $ts = wfTimestampNow();
516 $this->assertSame(
517 $lang->time( $ts ),
518 $msg->inLanguage( $lang )->timeParams( $ts )->plain(),
519 'time is handled correctly'
523 public function testUserGroupParams() {
524 $lang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'qqx' );
525 $msg = new RawMessage( '$1' );
526 $this->setUserLang( $lang );
527 $this->assertSame(
528 '(group-bot)',
529 $msg->userGroupParams( 'bot' )->plain(),
530 'user group is handled correctly'
534 public function testTimeperiodParams() {
535 $lang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'en' );
536 $msg = new RawMessage( '$1' );
538 $this->assertSame(
539 $lang->formatTimePeriod( 1234 ),
540 $msg->inLanguage( $lang )->timeperiodParams( 1234 )->plain(),
541 'timeperiodParams is handled correctly'
545 public function testSizeParams() {
546 $lang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'en' );
547 $msg = new RawMessage( '$1' );
549 $this->assertSame(
550 $lang->formatSize( 123456 ),
551 $msg->inLanguage( $lang )->sizeParams( 123456 )->plain(),
552 'sizeParams is handled correctly'
556 public function testBitrateParams() {
557 $lang = $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'en' );
558 $msg = new RawMessage( '$1' );
560 $this->assertSame(
561 $lang->formatBitrate( 123456 ),
562 $msg->inLanguage( $lang )->bitrateParams( 123456 )->plain(),
563 'bitrateParams is handled correctly'
567 public static function providePlaintextParams() {
568 return [
570 'one $2 <div>foo</div> [[Bar]] {{Baz}} &lt;',
571 'plain',
575 // expect
576 'one $2 <div>foo</div> [[Bar]] {{Baz}} &lt;',
577 // format
578 'text',
581 'one $2 &lt;div&gt;foo&lt;/div&gt; [[Bar]] {{Baz}} &amp;lt;',
582 'escaped',
586 'one $2 &lt;div&gt;foo&lt;/div&gt; [[Bar]] {{Baz}} &amp;lt;',
587 'parse',
591 "<p>one $2 &lt;div&gt;foo&lt;/div&gt; [[Bar]] {{Baz}} &amp;lt;\n</p>",
592 'parseAsBlock',
598 * @dataProvider providePlaintextParams
600 public function testPlaintextParams( $expect, $format ) {
601 $msg = new RawMessage( '$1 $2' );
602 $params = [
603 'one $2',
604 '<div>foo</div> [[Bar]] {{Baz}} &lt;',
606 $this->assertSame(
607 $expect,
608 $msg->inLanguage( 'en' )->plaintextParams( $params )->$format(),
609 "Fail formatting for $format"
613 public static function provideListParam() {
614 $lang = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'de' );
615 $msg1 = new Message( 'mainpage', [], $lang );
616 $msg2 = new RawMessage( "''link''", [], $lang );
618 return [
619 'Simple comma list' => [
620 [ 'a', 'b', 'c' ],
621 'comma',
622 'text',
623 'a, b, c'
626 'Simple semicolon list' => [
627 [ 'a', 'b', 'c' ],
628 'semicolon',
629 'text',
630 'a; b; c'
633 'Simple pipe list' => [
634 [ 'a', 'b', 'c' ],
635 'pipe',
636 'text',
637 'a | b | c'
640 'Simple text list' => [
641 [ 'a', 'b', 'c' ],
642 'text',
643 'text',
644 'a, b and c'
647 'Empty list' => [
649 'comma',
650 'text',
654 'List with all "before" params, ->text()' => [
655 [ "''link''", Message::numParam( 12345678 ) ],
656 'semicolon',
657 'text',
658 '\'\'link\'\'; 12,345,678'
661 'List with all "before" params, ->parse()' => [
662 [ "''link''", Message::numParam( 12345678 ) ],
663 'semicolon',
664 'parse',
665 '<i>link</i>; 12,345,678'
668 'List with all "after" params, ->text()' => [
669 [ $msg1, $msg2, Message::rawParam( '[[foo]]' ) ],
670 'semicolon',
671 'text',
672 'Main Page; \'\'link\'\'; [[foo]]'
675 'List with all "after" params, ->parse()' => [
676 [ $msg1, $msg2, Message::rawParam( '[[foo]]' ) ],
677 'semicolon',
678 'parse',
679 'Main Page; <i>link</i>; [[foo]]'
682 'List with both "before" and "after" params, ->text()' => [
683 [ $msg1, $msg2, Message::rawParam( '[[foo]]' ), "''link''", Message::numParam( 12345678 ) ],
684 'semicolon',
685 'text',
686 'Main Page; \'\'link\'\'; [[foo]]; \'\'link\'\'; 12,345,678'
689 'List with both "before" and "after" params, ->parse()' => [
690 [ $msg1, $msg2, Message::rawParam( '[[foo]]' ), "''link''", Message::numParam( 12345678 ) ],
691 'semicolon',
692 'parse',
693 'Main Page; <i>link</i>; [[foo]]; <i>link</i>; 12,345,678'
699 * @dataProvider provideListParam
701 public function testListParam( $list, $type, $format, $expect ) {
702 $msg = new RawMessage( '$1' );
703 $msg->params( [ Message::listParam( $list, $type ) ] );
704 $this->assertEquals(
705 $expect,
706 $msg->inLanguage( 'en' )->$format()
710 public function testMessageAsParam() {
711 $msg = new Message( 'returnto', [
712 new Message( 'apihelp-link', [
713 'foo', new Message( 'mainpage', [],
714 $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'en' ) )
715 ], $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'de' ) )
716 ], $this->getServiceContainer()->getLanguageFactory()->getLanguage( 'es' ) );
718 $this->assertEquals(
719 'Volver a [[Special:ApiHelp/foo|Página principal]].',
720 $msg->text(),
721 'Process with ->text()'
723 $this->assertEquals(
724 '<p>Volver a <a href="/wiki/Special:ApiHelp/foo" title="Special:ApiHelp/foo">Página '
725 . "principal</a>.\n</p>",
726 $msg->parseAsBlock(),
727 'Process with ->parseAsBlock()'
731 public static function provideParser() {
732 return [
734 "''&'' <x><!-- x -->",
735 'plain',
739 "''&'' <x><!-- x -->",
740 'text',
743 '<i>&amp;</i> &lt;x&gt;',
744 'parse',
748 "<p><i>&amp;</i> &lt;x&gt;\n</p>",
749 'parseAsBlock',
755 * @dataProvider provideParser
757 public function testParser( $expect, $format ) {
758 $msg = new RawMessage( "''&'' <x><!-- x -->" );
759 $this->assertSame(
760 $expect,
761 $msg->inLanguage( 'en' )->$format()
766 * @covers \LanguageQqx
768 public function testQqxPlaceholders() {
769 $this->assertSame(
770 '(test)',
771 wfMessage( 'test' )->inLanguage( 'qqx' )->text()
773 $this->assertSame(
774 '(test: a, b)',
775 wfMessage( 'test' )->params( 'a', 'b' )->inLanguage( 'qqx' )->text()
777 $this->assertSame(
778 '(test / other-test)',
779 wfMessageFallback( 'test', 'other-test' )->inLanguage( 'qqx' )->text()
781 $this->assertSame(
782 '(test / other-test: a, b)',
783 wfMessageFallback( 'test', 'other-test' )->params( 'a', 'b' )->inLanguage( 'qqx' )->text()
787 public function testInContentLanguage() {
788 $this->setUserLang( 'fr' );
790 // NOTE: make sure internal caching of the message text is reset appropriately
791 $msg = wfMessage( 'mainpage' );
792 $this->assertSame( 'Hauptseite', $msg->inLanguage( 'de' )->plain(), "inLanguage( 'de' )" );
793 $this->assertSame( 'Main Page', $msg->inContentLanguage()->plain(), "inContentLanguage()" );
794 $this->assertSame( 'Accueil', $msg->inLanguage( 'fr' )->plain(), "inLanguage( 'fr' )" );
797 public function testInContentLanguageOverride() {
798 $this->overrideConfigValue( MainConfigNames::ForceUIMsgAsContentMsg, [ 'mainpage' ] );
799 $this->setUserLang( 'fr' );
801 // NOTE: make sure internal caching of the message text is reset appropriately.
802 // NOTE: wgForceUIMsgAsContentMsg forces the messages *current* language to be used.
803 $msg = wfMessage( 'mainpage' );
804 $this->assertSame(
805 'Accueil',
806 $msg->inContentLanguage()->plain(),
807 'inContentLanguage() with ForceUIMsg override enabled'
809 $this->assertSame( 'Main Page', $msg->inLanguage( 'en' )->plain(), "inLanguage( 'en' )" );
810 $this->assertSame(
811 'Main Page',
812 $msg->inContentLanguage()->plain(),
813 'inContentLanguage() with ForceUIMsg override enabled'
815 $this->assertSame( 'Hauptseite', $msg->inLanguage( 'de' )->plain(), "inLanguage( 'de' )" );
818 public function testInLanguageThrows() {
819 $this->expectException( ParameterTypeException::class );
820 wfMessage( 'foo' )->inLanguage( 123 );
824 * @dataProvider provideSerializationRoundtrip
826 public function testSerialization( Message $msg, $serialized, $parsed ) {
827 $this->assertSame( $serialized, serialize( $msg ) );
828 $this->assertSame( $parsed, $msg->parse() );
832 * @dataProvider provideSerializationRoundtrip
833 * @dataProvider provideSerializationLegacy
835 public function testUnserialization( Message $msg, $serialized, $parsed ) {
836 $this->assertEquals( $msg, unserialize( $serialized ) );
837 $this->assertSame( $parsed, unserialize( $serialized )->parse() );
840 public function provideSerializationRoundtrip() {
841 // Test cases where we can test both serialization and unserialization.
842 // These really ought to use the MessageSerializationTestTrait, but
843 // doing so is complicated (T373719).
845 yield "Serializing raw parameters" => [
846 ( new Message( 'parentheses' ) )->rawParams( '<a>foo</a>' ),
847 'O:25:"MediaWiki\Message\Message":7:{s:9:"interface";b:1;s:8:"language";N;s:3:"key";s:11:"parentheses";s:9:"keysToTry";a:1:{i:0;s:11:"parentheses";}s:10:"parameters";a:1:{i:0;O:29:"Wikimedia\Message\ScalarParam":2:{s:7:"' . chr( 0 ) . '*' . chr( 0 ) . 'type";s:3:"raw";s:8:"' . chr( 0 ) . '*' . chr( 0 ) . 'value";s:10:"<a>foo</a>";}}s:11:"useDatabase";b:1;s:10:"titlevalue";N;}',
848 '(<a>foo</a>)',
851 yield "Serializing message with a context page" => [
852 ( new Message( 'rawmessage', [ '{{PAGENAME}}' ] ) )->page( PageReferenceValue::localReference( NS_MAIN, 'Testing' ) ),
853 'O:25:"MediaWiki\Message\Message":7:{s:9:"interface";b:1;s:8:"language";N;s:3:"key";s:10:"rawmessage";s:9:"keysToTry";a:1:{i:0;s:10:"rawmessage";}s:10:"parameters";a:1:{i:0;s:12:"{{PAGENAME}}";}s:11:"useDatabase";b:1;s:10:"titlevalue";a:2:{i:0;i:0;i:1;s:7:"Testing";}}',
854 'Testing',
857 yield "Serializing language" => [
858 ( new Message( 'mainpage' ) )->inLanguage( 'de' ),
859 'O:25:"MediaWiki\Message\Message":7:{s:9:"interface";b:0;s:8:"language";s:2:"de";s:3:"key";s:8:"mainpage";s:9:"keysToTry";a:1:{i:0;s:8:"mainpage";}s:10:"parameters";a:0:{}s:11:"useDatabase";b:1;s:10:"titlevalue";N;}',
860 'Hauptseite',
864 public function provideSerializationLegacy() {
865 // Test cases where we can test only unserialization, because the serialization format changed.
867 yield "MW 1.42: Magic arrays instead of MessageParam objects" => [
868 ( new Message( 'parentheses' ) )->rawParams( '<a>foo</a>' ),
869 'O:25:"MediaWiki\Message\Message":7:{s:9:"interface";b:1;s:8:"language";N;s:3:"key";s:11:"parentheses";s:9:"keysToTry";a:1:{i:0;s:11:"parentheses";}s:10:"parameters";a:1:{i:0;a:1:{s:3:"raw";s:10:"<a>foo</a>";}}s:11:"useDatabase";b:1;s:10:"titlevalue";N;}',
870 '(<a>foo</a>)',
873 yield "MW 1.41: Un-namespaced class" => [
874 new Message( 'mainpage' ),
875 'O:7:"Message":7:{s:9:"interface";b:1;s:8:"language";N;s:3:"key";s:8:"mainpage";s:9:"keysToTry";a:1:{i:0;s:8:"mainpage";}s:10:"parameters";a:0:{}s:11:"useDatabase";b:1;s:10:"titlevalue";N;}',
876 'Main Page',
879 yield "MW 1.34: 'titlestr' instead of 'titlevalue'" => [
880 ( new Message( 'rawmessage', [ '{{PAGENAME}}' ] ) )->title( Title::newFromText( 'Testing' ) ),
881 'C:7:"Message":242:{a:8:{s:9:"interface";b:1;s:8:"language";b:0;s:3:"key";s:10:"rawmessage";s:9:"keysToTry";a:1:{i:0;s:10:"rawmessage";}s:10:"parameters";a:1:{i:0;s:12:"{{PAGENAME}}";}s:6:"format";s:5:"parse";s:11:"useDatabase";b:1;s:8:"titlestr";s:7:"Testing";}}',
882 'Testing',
887 * @dataProvider provideNewFromSpecifier
889 public function testNewFromSpecifier( $value, $expectedText ) {
890 $message = Message::newFromSpecifier( $value );
891 $this->assertInstanceOf( Message::class, $message );
892 if ( $value instanceof Message ) {
893 $this->assertInstanceOf( get_class( $value ), $message );
894 $this->assertEquals( $value, $message );
896 $this->assertSame( $expectedText, $message->text() );
899 public function provideNewFromSpecifier() {
900 $messageSpecifier = $this->getMockForAbstractClass( MessageSpecifier::class );
901 $messageSpecifier->method( 'getKey' )->willReturn( 'mainpage' );
902 $messageSpecifier->method( 'getParams' )->willReturn( [] );
904 return [
905 'string' => [ 'mainpage', 'Main Page' ],
906 'array' => [ [ 'new-messages', 'foo', 'bar' ], 'You have foo (bar).' ],
907 'Message' => [ new Message( 'new-messages', [ 'foo', 'bar' ] ), 'You have foo (bar).' ],
908 'RawMessage' => [ new RawMessage( 'foo ($1)', [ 'bar' ] ), 'foo (bar)' ],
909 'ApiMessage' => [ new ApiMessage( [ 'mainpage' ], 'code', [ 'data' ] ), 'Main Page' ],
910 'MessageSpecifier' => [ $messageSpecifier, 'Main Page' ],
911 'nested RawMessage' => [ [ new RawMessage( 'foo ($1)', [ 'bar' ] ) ], 'foo (bar)' ],