Merge ".mailmap: Correct two contributor names"
[mediawiki.git] / tests / phpunit / includes / Status / StatusFormatterTest.php
blobb16d8064b2a91f54145fc76660cee16fa9c36417
1 <?php
3 use MediaWiki\Context\RequestContext;
4 use MediaWiki\Language\RawMessage;
5 use MediaWiki\Message\Message;
6 use MediaWiki\Parser\ParserOutput;
7 use MediaWiki\Status\StatusFormatter;
8 use MediaWiki\User\User;
9 use Psr\Log\Test\TestLogger;
10 use Wikimedia\Message\MessageValue;
11 use Wikimedia\TestingAccessWrapper;
13 /**
14 * @covers \MediaWiki\Status\StatusFormatter
16 class StatusFormatterTest extends MediaWikiLangTestCase {
18 private ?TestLogger $logger;
20 protected function setUp(): void {
21 parent::setUp();
23 $this->logger = new TestLogger();
26 protected function tearDown(): void {
27 parent::tearDown();
28 $this->logger = null;
31 private function getFormatter( $lang = 'en' ) {
32 $localizer = new class() implements MessageLocalizer {
33 public $lang;
35 public function msg( $key, ...$params ) {
36 return wfMessage( $key, ...$params )->inLanguage( $this->lang );
40 $cache = $this->createNoOpMock( MessageCache::class, [ 'parseWithPostprocessing' ] );
41 $cache->method( 'parseWithPostprocessing' )->willReturnCallback(
42 static function ( $text, ...$args ) {
43 $text = html_entity_decode( $text, ENT_QUOTES | ENT_HTML5 );
44 return new ParserOutput( "<p>" . trim( $text ) . "\n</p>" );
48 $localizer->lang = $lang;
50 return new StatusFormatter( $localizer, $cache, $this->logger );
53 /**
54 * @dataProvider provideCleanParams
56 public function testCleanParams( $cleanCallback, $params, $expected, $unexpected ) {
57 $status = new StatusValue();
58 $status->warning( 'ok', ...$params );
60 $formatter = $this->getFormatter( 'qqx' );
61 $options = [ 'cleanCallback' => $cleanCallback ];
63 $wikitext = $formatter->getWikiText( $status, $options );
64 $this->assertStringContainsString( $expected, $wikitext );
65 $this->assertStringNotContainsString( $unexpected, $wikitext );
67 $html = $formatter->getHTML( $status, $options );
68 $this->assertStringContainsString( $expected, $html );
69 $this->assertStringNotContainsString( $unexpected, $html );
72 public static function provideCleanParams() {
73 $cleanCallback = static function ( $value ) {
74 return 'xxx';
77 return [
78 [ false, [ 'secret' ], 'secret', 'xxx' ],
79 [ $cleanCallback, [ 'secret' ], 'xxx', 'secret' ],
83 /**
84 * @dataProvider provideGetWikiTextAndHtml
86 public function testGetWikiText(
87 StatusValue $status, $wikitext, $wrappedWikitext, $html, $wrappedHtml
88 ) {
89 $formatter = $this->getFormatter();
90 $this->assertEquals( $wikitext, $formatter->getWikiText( $status ) );
92 $this->assertEquals(
93 $wrappedWikitext,
94 $formatter->getWikiText(
95 $status,
97 'shortContext' => 'wrap-short',
98 'longContext' => 'wrap-long',
99 'lang' => 'qqx',
106 * @dataProvider provideGetWikiTextAndHtml
108 public function testGetHtml(
109 StatusValue $status,
110 $wikitext,
111 $wrappedWikitext,
112 $html,
113 $wrappedHtml,
114 ?string $expectedWarning = null
116 $formatter = $this->getFormatter();
117 $this->assertEquals( $html, $formatter->getHTML( $status ) );
119 $this->assertEquals(
120 $wrappedHtml,
121 $formatter->getHTML(
122 $status,
124 'shortContext' => 'wrap-short',
125 'longContext' => 'wrap-long',
126 'lang' => 'qqx',
131 if ( $expectedWarning !== null ) {
132 $this->assertTrue( $this->logger->hasWarningThatContains( $expectedWarning ) );
133 } else {
134 $this->assertFalse( $this->logger->hasWarningRecords() );
139 * @return array Array of arrays with values;
140 * 0 => status object
141 * 1 => expected string (with no context)
143 public static function provideGetWikiTextAndHtml() {
144 $testCases = [];
146 $testCases['GoodStatus'] = [
147 new StatusValue(),
148 "Internal error: MediaWiki\Status\StatusFormatter::getWikiText called for a good result, this is incorrect&#10;",
149 "(wrap-short: (internalerror_info: MediaWiki\Status\StatusFormatter::getWikiText called for a good result, " .
150 "this is incorrect&#10;))",
151 "<p>Internal error: MediaWiki\Status\StatusFormatter::getWikiText called for a good result, this is incorrect\n</p>",
152 "<p>(wrap-short: (internalerror_info: MediaWiki\Status\StatusFormatter::getWikiText called for a good result, " .
153 "this is incorrect\n))\n</p>",
154 'MediaWiki\Status\StatusFormatter::getWikiText called for a good result, this is incorrect'
157 $status = new StatusValue();
158 $status->setOK( false );
159 $testCases['GoodButNoError'] = [
160 $status,
161 "Internal error: MediaWiki\Status\StatusFormatter::getWikiText: Invalid result object: no error text but not OK&#10;",
162 "(wrap-short: (internalerror_info: MediaWiki\Status\StatusFormatter::getWikiText: Invalid result object: " .
163 "no error text but not OK&#10;))",
164 "<p>Internal error: MediaWiki\Status\StatusFormatter::getWikiText: Invalid result object: no error text but not OK\n</p>",
165 "<p>(wrap-short: (internalerror_info: MediaWiki\Status\StatusFormatter::getWikiText: Invalid result object: " .
166 "no error text but not OK\n))\n</p>",
167 'MediaWiki\Status\StatusFormatter::getWikiText: Invalid result object: no error text but not OK'
170 $status = new StatusValue();
171 $status->warning( 'fooBar!' );
172 $testCases['1StringWarning'] = [
173 $status,
174 "⧼fooBar!⧽",
175 "(wrap-short: (fooBar!))",
176 "<p>⧼fooBar!⧽\n</p>",
177 "<p>(wrap-short: (fooBar!))\n</p>",
180 $status = new StatusValue();
181 $status->warning( 'fooBar!' );
182 $status->warning( 'fooBar2!' );
183 $testCases['2StringWarnings'] = [
184 $status,
185 "<ul>\n<li>\n⧼fooBar!⧽\n</li>\n<li>\n⧼fooBar2!⧽\n</li>\n</ul>\n",
186 "(wrap-long: <ul>\n<li>\n(fooBar!)\n</li>\n<li>\n(fooBar2!)\n</li>\n</ul>\n)",
187 "<p><ul>\n<li>\n⧼fooBar!⧽\n</li>\n<li>\n⧼fooBar2!⧽\n</li>\n</ul>\n</p>",
188 "<p>(wrap-long: <ul>\n<li>\n(fooBar!)\n</li>\n<li>\n(fooBar2!)\n</li>\n</ul>\n)\n</p>",
191 $status = new StatusValue();
192 $status->warning( new Message( 'fooBar!', [ 'foo', 'bar' ] ) );
193 $testCases['1MessageWarning'] = [
194 $status,
195 "⧼fooBar!⧽",
196 "(wrap-short: (fooBar!: foo, bar))",
197 "<p>⧼fooBar!⧽\n</p>",
198 "<p>(wrap-short: (fooBar!: foo, bar))\n</p>",
201 $status = new StatusValue();
202 $status->warning( new Message( 'fooBar!', [ 'foo', 'bar' ] ) );
203 $status->warning( new Message( 'fooBar2!' ) );
204 $testCases['2MessageWarnings'] = [
205 $status,
206 "<ul>\n<li>\n⧼fooBar!⧽\n</li>\n<li>\n⧼fooBar2!⧽\n</li>\n</ul>\n",
207 "(wrap-long: <ul>\n<li>\n(fooBar!: foo, bar)\n</li>\n<li>\n(fooBar2!)\n</li>\n</ul>\n)",
208 "<p><ul>\n<li>\n⧼fooBar!⧽\n</li>\n<li>\n⧼fooBar2!⧽\n</li>\n</ul>\n</p>",
209 "<p>(wrap-long: <ul>\n<li>\n(fooBar!: foo, bar)\n</li>\n<li>\n(fooBar2!)\n</li>\n</ul>\n)\n</p>",
212 return $testCases;
215 private static function sanitizedMessageParams( Message $message ) {
216 return array_map( static function ( $p ) {
217 return $p instanceof Message
219 'key' => $p->getKey(),
220 'params' => self::sanitizedMessageParams( $p ),
221 'lang' => $p->getLanguage()->getCode(),
223 : $p;
224 }, $message instanceof RawMessage ? $message->getParamsOfRawMessage() : $message->getParams() );
227 private static function sanitizedMessageKey( Message $message ) {
228 return $message instanceof RawMessage ? $message->getTextOfRawMessage() : $message->getKey();
232 * @dataProvider provideGetMessage
234 public function testGetMessage(
235 StatusValue $status,
236 $expectedParams,
237 $expectedKey,
238 $expectedWrapper,
239 ?string $expectedWarning = null
241 $formatter = $this->getFormatter();
242 $message = $formatter->getMessage( $status, [ 'lang' => 'qqx' ] );
243 $this->assertInstanceOf( Message::class, $message );
244 $this->assertEquals( $expectedParams, self::sanitizedMessageParams( $message ),
245 'Message::getParams' );
246 $this->assertEquals( $expectedKey, self::sanitizedMessageKey( $message ), 'Message::getKey' );
248 $message = $formatter->getMessage(
249 $status,
251 'shortContext' => 'wrapper-short',
252 'longContext' => 'wrapper-long',
255 $this->assertInstanceOf( Message::class, $message );
256 $this->assertEquals( $expectedWrapper, $message->getKey(), 'Message::getKey with wrappers' );
257 $this->assertCount( 1, $message->getParams(), 'Message::getParams with wrappers' );
259 $message = $formatter->getMessage( $status, [ 'shortContext' => 'wrapper' ] );
260 $this->assertInstanceOf( Message::class, $message );
261 $this->assertEquals( 'wrapper', $message->getKey(), 'Message::getKey with wrappers' );
262 $this->assertCount( 1, $message->getParams(), 'Message::getParams with wrappers' );
264 $message = $formatter->getMessage( $status, [ 'longContext' => 'wrapper' ] );
265 $this->assertInstanceOf( Message::class, $message );
266 $this->assertEquals( 'wrapper', $message->getKey(), 'Message::getKey with wrappers' );
267 $this->assertCount( 1, $message->getParams(), 'Message::getParams with wrappers' );
269 if ( $expectedWarning !== null ) {
270 $this->assertTrue( $this->logger->hasWarningThatContains( $expectedWarning ) );
271 } else {
272 $this->assertFalse( $this->logger->hasWarningRecords() );
277 * @return array Array of arrays with values;
278 * 0 => status object
279 * 1 => expected Message parameters (with no context)
280 * 2 => expected Message key
282 public static function provideGetMessage() {
283 $testCases = [];
285 $testCases['GoodStatus'] = [
286 new StatusValue(),
287 [ "MediaWiki\Status\StatusFormatter::getMessage called for a good result, this is incorrect&#10;" ],
288 'internalerror_info',
289 'wrapper-short',
290 'MediaWiki\Status\StatusFormatter::getMessage called for a good result, this is incorrect'
293 $status = new StatusValue();
294 $status->setOK( false );
295 $testCases['GoodButNoError'] = [
296 $status,
297 [ "MediaWiki\Status\StatusFormatter::getMessage: Invalid result object: no error text but not OK&#10;" ],
298 'internalerror_info',
299 'wrapper-short',
300 'MediaWiki\Status\StatusFormatter::getMessage: Invalid result object: no error text but not OK'
303 $status = new StatusValue();
304 $status->warning( 'fooBar!' );
305 $testCases['1StringWarning'] = [
306 $status,
308 'fooBar!',
309 'wrapper-short'
312 $status = new StatusValue();
313 $status->warning( 'fooBar!' );
314 $status->warning( 'fooBar2!' );
315 $testCases[ '2StringWarnings' ] = [
316 $status,
318 [ 'key' => 'fooBar!', 'params' => [], 'lang' => 'qqx' ],
319 [ 'key' => 'fooBar2!', 'params' => [], 'lang' => 'qqx' ]
321 "* \$1\n* \$2",
322 'wrapper-long'
325 $status = new StatusValue();
326 $status->warning( new Message( 'fooBar!', [ 'foo', 'bar' ] ) );
327 $testCases['1MessageWarning'] = [
328 $status,
329 [ 'foo', 'bar' ],
330 'fooBar!',
331 'wrapper-short'
334 $status = new StatusValue();
335 $status->warning( new MessageValue( 'fooBar!', [ 'foo', 'bar' ] ) );
336 $status->warning( new MessageValue( 'fooBar2!' ) );
337 $testCases['2MessageWarnings'] = [
338 $status,
340 [ 'key' => 'fooBar!', 'params' => [ 'foo', 'bar' ], 'lang' => 'qqx' ],
341 [ 'key' => 'fooBar2!', 'params' => [], 'lang' => 'qqx' ]
343 "* \$1\n* \$2",
344 'wrapper-long'
347 return $testCases;
351 * @dataProvider provideGetPsr3MessageAndContext
353 public function testGetPsr3MessageAndContext(
354 array $errors,
355 string $expectedMessage,
356 array $expectedContext
358 // set up a rawmessage_2 message, which is just like rawmessage but doesn't trigger
359 // the special-casing in StatusFormatter::getPsr3MessageAndContext
360 $this->setTemporaryHook( 'MessageCacheFetchOverrides', static function ( &$overrides ) {
361 $overrides['rawmessage_2'] = 'rawmessage';
362 }, false );
364 $status = new StatusValue();
365 foreach ( $errors as $error ) {
366 $status->error( ...$error );
369 $formatter = $this->getFormatter();
371 [ $actualMessage, $actualContext ] = $formatter->getPsr3MessageAndContext( $status );
372 $this->assertSame( $expectedMessage, $actualMessage );
373 $this->assertSame( $expectedContext, $actualContext );
376 public static function provideGetPsr3MessageAndContext() {
377 return [
378 // parameters to StatusValue::error() calls as array of arrays; expected message; expected context
379 'no errors' => [
381 "Internal error: MediaWiki\Status\StatusFormatter::getWikiText called for a good result, this is incorrect&#10;",
384 // make sure that the rawmessage_2 hack works as the following tests rely on it
385 'rawmessage_2' => [
386 [ [ 'rawmessage_2', 'foo' ] ],
387 '{parameter1}',
388 [ 'parameter1' => 'foo' ],
390 'two errors' => [
391 [ [ 'rawmessage_2', 'foo' ], [ 'rawmessage_2', 'bar' ] ],
392 "<ul>\n<li>\nfoo\n</li>\n<li>\nbar\n</li>\n</ul>\n",
395 'unknown subclass' => [
396 // phpcs:ignore Squiz.WhiteSpace.ScopeClosingBrace.ContentBefore
397 [ [ new class( 'rawmessage_2', [ 'foo' ] ) extends Message {} ] ],
398 'foo',
401 'non-scalar parameter' => [
402 [ [ new Message( 'rawmessage_2', [ new Message( 'rawmessage_2', [ 'foo' ] ) ] ) ] ],
403 'foo',
406 'one parameter' => [
407 [ [ 'apiwarn-invalidtitle', 'foo' ] ],
408 '"{parameter1}" is not a valid title.',
409 [ 'parameter1' => 'foo' ],
411 'multiple parameters' => [
412 [ [ 'api-exception-trace', 'foo', 'bar', 'baz', 'boom' ] ],
413 "{parameter1} at {parameter2}({parameter3})\n{parameter4}",
414 [ 'parameter1' => 'foo', 'parameter2' => 'bar', 'parameter3' => 'baz', 'parameter4' => 'boom' ],
416 'formatted parameter' => [
417 [ [ 'apiwarn-invalidtitle', Message::numParam( 1000000 ) ] ],
418 '"{parameter1}" is not a valid title.',
419 [ 'parameter1' => 1000000 ],
421 'rawmessage' => [
422 [ [ 'rawmessage', 'foo' ] ],
423 'foo',
426 'RawMessage' => [
427 [ [ new RawMessage( 'foo $1 baz', [ 'bar' ] ) ] ],
428 'foo {parameter1} baz',
429 [ 'parameter1' => 'bar' ],
434 public function testGetErrorMessage() {
435 $formatter = $this->getFormatter();
436 /** @var StatusFormatter $formatter */
437 $formatter = TestingAccessWrapper::newFromObject( $formatter );
438 $key = 'foo';
439 $params = [ 'bar' ];
441 $message = $formatter->getErrorMessage( [ $key, ...$params ] );
442 $this->assertInstanceOf( Message::class, $message );
443 $this->assertEquals( $key, $message->getKey() );
444 $this->assertEquals( $params, $message->getParams() );
447 public function testGetErrorMessageComplexParam() {
448 $formatter = $this->getFormatter();
449 /** @var StatusFormatter $formatter */
450 $formatter = TestingAccessWrapper::newFromObject( $formatter );
451 $key = 'foo';
452 $params = [ 'bar', Message::numParam( 5 ) ];
454 $message = $formatter->getErrorMessage( [ $key, ...$params ] );
455 $this->assertInstanceOf( Message::class, $message );
456 $this->assertEquals( $key, $message->getKey() );
457 $this->assertEquals( $params, $message->getParams() );
460 public function testGetErrorMessageArray() {
461 $formatter = $this->getFormatter();
462 $formatter = TestingAccessWrapper::newFromObject( $formatter );
463 $key = 'foo';
464 $params = [ 'bar' ];
466 /** @var Message[] $messageArray */
467 $messageArray = $formatter->getErrorMessageArray(
469 [ $key, ...$params ],
470 [ $key, ...$params ],
474 $this->assertIsArray( $messageArray );
475 $this->assertCount( 2, $messageArray );
476 foreach ( $messageArray as $message ) {
477 $this->assertInstanceOf( Message::class, $message );
478 $this->assertEquals( $key, $message->getKey() );
479 $this->assertEquals( $params, $message->getParams() );
483 public function testUserLanguageNotLoaded() {
484 // Confirm that the user language is not loaded from the database when
485 // formatting an error in a specific language
486 $this->getServiceContainer()->disableService( 'UserOptionsLookup' );
487 $context = RequestContext::getMain();
488 $user = new User;
489 $user->setName( 'Test' );
490 $context->setUser( $user );
491 $this->getServiceContainer()
492 ->getFormatterFactory()
493 ->getStatusFormatter( RequestContext::getMain() )
494 ->getWikiText(
495 StatusValue::newFatal( 'apierror-badquery' ),
496 [ 'lang' => 'en' ]
498 $this->assertTrue( true );