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