This takes us one step closer to deprecating Status, so we can isolate StatusValue from presentation logic. FormatterFactory is introduced as a mechanism for getting instance of formatters that need access to the user interface language and other request dependent information. Usage is demonstrated in thumb.php, SpecialCreateAccount, and SearchHandler. The examples indicates that there is no work do be done around ErrorPageError and LocalizedHttpException. Change-Id: I7fe5fee24cadf934e578c36856cc5d45fb9d0981
435 lines
13 KiB
PHP
435 lines
13 KiB
PHP
<?php
|
|
|
|
use MediaWiki\Language\RawMessage;
|
|
use MediaWiki\Status\StatusFormatter;
|
|
use Wikimedia\Message\MessageValue;
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
/**
|
|
* @covers MediaWiki\Status\StatusFormatter
|
|
*/
|
|
class StatusFormatterTest extends MediaWikiLangTestCase {
|
|
|
|
private function getFormatter( $lang = 'en' ) {
|
|
$localizer = new class() implements MessageLocalizer {
|
|
public $lang;
|
|
|
|
public function msg( $key, ...$params ) {
|
|
return wfMessage( $key, ...$params )->inLanguage( $this->lang );
|
|
}
|
|
};
|
|
|
|
$cache = $this->createNoOpMock( MessageCache::class, [ 'parse' ] );
|
|
$cache->method( 'parse' )->willReturnCallback(
|
|
static function ( $text ) {
|
|
return "<p>" . htmlspecialchars( trim( $text ) ) . "\n</p>";
|
|
}
|
|
);
|
|
|
|
$localizer->lang = $lang;
|
|
|
|
return new StatusFormatter( $localizer, $cache );
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideCleanParams
|
|
*/
|
|
public function testCleanParams( $cleanCallback, $params, $expected, $unexpected ) {
|
|
$status = new StatusValue();
|
|
$status->warning( 'ok', ...$params );
|
|
|
|
$formatter = $this->getFormatter( 'qqx' );
|
|
$options = [ 'cleanCallback' => $cleanCallback ];
|
|
|
|
$wikitext = $formatter->getWikiText( $status, $options );
|
|
$this->assertStringContainsString( $expected, $wikitext );
|
|
$this->assertStringNotContainsString( $unexpected, $wikitext );
|
|
|
|
$html = $formatter->getHTML( $status, $options );
|
|
$this->assertStringContainsString( $expected, $html );
|
|
$this->assertStringNotContainsString( $unexpected, $html );
|
|
}
|
|
|
|
public static function provideCleanParams() {
|
|
$cleanCallback = static function ( $value ) {
|
|
return 'xxx';
|
|
};
|
|
|
|
return [
|
|
[ false, [ 'secret' ], 'secret', 'xxx' ],
|
|
[ $cleanCallback, [ 'secret' ], 'xxx', 'secret' ],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideGetWikiTextAndHtml
|
|
*/
|
|
public function testGetWikiText(
|
|
StatusValue $status, $wikitext, $wrappedWikitext, $html, $wrappedHtml
|
|
) {
|
|
$formatter = $this->getFormatter();
|
|
$this->assertEquals( $wikitext, $formatter->getWikiText( $status ) );
|
|
|
|
$this->assertEquals(
|
|
$wrappedWikitext,
|
|
$formatter->getWikiText(
|
|
$status,
|
|
[
|
|
'shortContext' => 'wrap-short',
|
|
'longContext' => 'wrap-long',
|
|
'lang' => 'qqx',
|
|
]
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideGetWikiTextAndHtml
|
|
*/
|
|
public function testGetHtml(
|
|
StatusValue $status, $wikitext, $wrappedWikitext, $html, $wrappedHtml
|
|
) {
|
|
$formatter = $this->getFormatter();
|
|
$this->assertEquals( $html, $formatter->getHTML( $status ) );
|
|
|
|
$this->assertEquals(
|
|
$wrappedHtml,
|
|
$formatter->getHTML(
|
|
$status,
|
|
[
|
|
'shortContext' => 'wrap-short',
|
|
'longContext' => 'wrap-long',
|
|
'lang' => 'qqx',
|
|
]
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return array Array of arrays with values;
|
|
* 0 => status object
|
|
* 1 => expected string (with no context)
|
|
*/
|
|
public static function provideGetWikiTextAndHtml() {
|
|
$testCases = [];
|
|
|
|
$testCases['GoodStatus'] = [
|
|
new StatusValue(),
|
|
"Internal error: MediaWiki\Status\StatusFormatter::getWikiText called for a good result, this is incorrect\n",
|
|
"(wrap-short: (internalerror_info: MediaWiki\Status\StatusFormatter::getWikiText called for a good result, " .
|
|
"this is incorrect\n))",
|
|
"<p>Internal error: MediaWiki\Status\StatusFormatter::getWikiText called for a good result, this is incorrect\n</p>",
|
|
"<p>(wrap-short: (internalerror_info: MediaWiki\Status\StatusFormatter::getWikiText called for a good result, " .
|
|
"this is incorrect\n))\n</p>",
|
|
];
|
|
|
|
$status = new StatusValue();
|
|
$status->setOK( false );
|
|
$testCases['GoodButNoError'] = [
|
|
$status,
|
|
"Internal error: MediaWiki\Status\StatusFormatter::getWikiText: Invalid result object: no error text but not OK\n",
|
|
"(wrap-short: (internalerror_info: MediaWiki\Status\StatusFormatter::getWikiText: Invalid result object: " .
|
|
"no error text but not OK\n))",
|
|
"<p>Internal error: MediaWiki\Status\StatusFormatter::getWikiText: Invalid result object: no error text but not OK\n</p>",
|
|
"<p>(wrap-short: (internalerror_info: MediaWiki\Status\StatusFormatter::getWikiText: Invalid result object: " .
|
|
"no error text but not OK\n))\n</p>",
|
|
];
|
|
|
|
$status = new StatusValue();
|
|
$status->warning( 'fooBar!' );
|
|
$testCases['1StringWarning'] = [
|
|
$status,
|
|
"⧼fooBar!⧽",
|
|
"(wrap-short: (fooBar!))",
|
|
"<p>⧼fooBar!⧽\n</p>",
|
|
"<p>(wrap-short: (fooBar!))\n</p>",
|
|
];
|
|
|
|
$status = new StatusValue();
|
|
$status->warning( 'fooBar!' );
|
|
$status->warning( 'fooBar2!' );
|
|
$testCases['2StringWarnings'] = [
|
|
$status,
|
|
"* ⧼fooBar!⧽\n* ⧼fooBar2!⧽\n",
|
|
"(wrap-long: * (fooBar!)\n* (fooBar2!)\n)",
|
|
"<p>* ⧼fooBar!⧽\n* ⧼fooBar2!⧽\n</p>",
|
|
"<p>(wrap-long: * (fooBar!)\n* (fooBar2!)\n)\n</p>",
|
|
];
|
|
|
|
$status = new StatusValue();
|
|
$status->warning( new Message( 'fooBar!', [ 'foo', 'bar' ] ) );
|
|
$testCases['1MessageWarning'] = [
|
|
$status,
|
|
"⧼fooBar!⧽",
|
|
"(wrap-short: (fooBar!: foo, bar))",
|
|
"<p>⧼fooBar!⧽\n</p>",
|
|
"<p>(wrap-short: (fooBar!: foo, bar))\n</p>",
|
|
];
|
|
|
|
$status = new StatusValue();
|
|
$status->warning( new Message( 'fooBar!', [ 'foo', 'bar' ] ) );
|
|
$status->warning( new Message( 'fooBar2!' ) );
|
|
$testCases['2MessageWarnings'] = [
|
|
$status,
|
|
"* ⧼fooBar!⧽\n* ⧼fooBar2!⧽\n",
|
|
"(wrap-long: * (fooBar!: foo, bar)\n* (fooBar2!)\n)",
|
|
"<p>* ⧼fooBar!⧽\n* ⧼fooBar2!⧽\n</p>",
|
|
"<p>(wrap-long: * (fooBar!: foo, bar)\n* (fooBar2!)\n)\n</p>",
|
|
];
|
|
|
|
return $testCases;
|
|
}
|
|
|
|
private static function sanitizedMessageParams( Message $message ) {
|
|
return array_map( static function ( $p ) {
|
|
return $p instanceof Message
|
|
? [
|
|
'key' => $p->getKey(),
|
|
'params' => self::sanitizedMessageParams( $p ),
|
|
'lang' => $p->getLanguage()->getCode(),
|
|
]
|
|
: $p;
|
|
}, $message->getParams() );
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideGetMessage
|
|
*/
|
|
public function testGetMessage(
|
|
StatusValue $status, $expectedParams, $expectedKey, $expectedWrapper
|
|
) {
|
|
$formatter = $this->getFormatter();
|
|
$message = $formatter->getMessage( $status, [ 'lang' => 'qqx' ] );
|
|
$this->assertInstanceOf( Message::class, $message );
|
|
$this->assertEquals( $expectedParams, self::sanitizedMessageParams( $message ),
|
|
'Message::getParams' );
|
|
$this->assertEquals( $expectedKey, $message->getKey(), 'Message::getKey' );
|
|
|
|
$message = $formatter->getMessage(
|
|
$status,
|
|
[
|
|
'shortContext' => 'wrapper-short',
|
|
'longContext' => 'wrapper-long',
|
|
]
|
|
);
|
|
$this->assertInstanceOf( Message::class, $message );
|
|
$this->assertEquals( $expectedWrapper, $message->getKey(), 'Message::getKey with wrappers' );
|
|
$this->assertCount( 1, $message->getParams(), 'Message::getParams with wrappers' );
|
|
|
|
$message = $formatter->getMessage( $status, [ 'shortContext' => 'wrapper' ] );
|
|
$this->assertInstanceOf( Message::class, $message );
|
|
$this->assertEquals( 'wrapper', $message->getKey(), 'Message::getKey with wrappers' );
|
|
$this->assertCount( 1, $message->getParams(), 'Message::getParams with wrappers' );
|
|
|
|
$message = $formatter->getMessage( $status, [ 'longContext' => 'wrapper' ] );
|
|
$this->assertInstanceOf( Message::class, $message );
|
|
$this->assertEquals( 'wrapper', $message->getKey(), 'Message::getKey with wrappers' );
|
|
$this->assertCount( 1, $message->getParams(), 'Message::getParams with wrappers' );
|
|
}
|
|
|
|
/**
|
|
* @return array Array of arrays with values;
|
|
* 0 => status object
|
|
* 1 => expected Message parameters (with no context)
|
|
* 2 => expected Message key
|
|
*/
|
|
public static function provideGetMessage() {
|
|
$testCases = [];
|
|
|
|
$testCases['GoodStatus'] = [
|
|
new StatusValue(),
|
|
[ "MediaWiki\Status\StatusFormatter::getMessage called for a good result, this is incorrect\n" ],
|
|
'internalerror_info',
|
|
'wrapper-short'
|
|
];
|
|
|
|
$status = new StatusValue();
|
|
$status->setOK( false );
|
|
$testCases['GoodButNoError'] = [
|
|
$status,
|
|
[ "MediaWiki\Status\StatusFormatter::getMessage: Invalid result object: no error text but not OK\n" ],
|
|
'internalerror_info',
|
|
'wrapper-short'
|
|
];
|
|
|
|
$status = new StatusValue();
|
|
$status->warning( 'fooBar!' );
|
|
$testCases['1StringWarning'] = [
|
|
$status,
|
|
[],
|
|
'fooBar!',
|
|
'wrapper-short'
|
|
];
|
|
|
|
$status = new StatusValue();
|
|
$status->warning( 'fooBar!' );
|
|
$status->warning( 'fooBar2!' );
|
|
$testCases[ '2StringWarnings' ] = [
|
|
$status,
|
|
[
|
|
[ 'key' => 'fooBar!', 'params' => [], 'lang' => 'qqx' ],
|
|
[ 'key' => 'fooBar2!', 'params' => [], 'lang' => 'qqx' ]
|
|
],
|
|
"* \$1\n* \$2",
|
|
'wrapper-long'
|
|
];
|
|
|
|
$status = new StatusValue();
|
|
$status->warning( new Message( 'fooBar!', [ 'foo', 'bar' ] ) );
|
|
$testCases['1MessageWarning'] = [
|
|
$status,
|
|
[ 'foo', 'bar' ],
|
|
'fooBar!',
|
|
'wrapper-short'
|
|
];
|
|
|
|
$status = new StatusValue();
|
|
$status->warning( new MessageValue( 'fooBar!', [ 'foo', 'bar' ] ) );
|
|
$status->warning( new MessageValue( 'fooBar2!' ) );
|
|
$testCases['2MessageWarnings'] = [
|
|
$status,
|
|
[
|
|
[ 'key' => 'fooBar!', 'params' => [ 'foo', 'bar' ], 'lang' => 'qqx' ],
|
|
[ 'key' => 'fooBar2!', 'params' => [], 'lang' => 'qqx' ]
|
|
],
|
|
"* \$1\n* \$2",
|
|
'wrapper-long'
|
|
];
|
|
|
|
return $testCases;
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideGetPsr3MessageAndContext
|
|
*/
|
|
public function testGetPsr3MessageAndContext(
|
|
array $errors,
|
|
string $expectedMessage,
|
|
array $expectedContext
|
|
) {
|
|
// set up a rawmessage_2 message, which is just like rawmessage but doesn't trigger
|
|
// the special-casing in StatusFormatter::getPsr3MessageAndContext
|
|
$this->setTemporaryHook( 'MessageCacheFetchOverrides', static function ( &$overrides ) {
|
|
$overrides['rawmessage_2'] = 'rawmessage';
|
|
}, false );
|
|
|
|
$status = new StatusValue();
|
|
foreach ( $errors as $error ) {
|
|
$status->error( ...$error );
|
|
}
|
|
|
|
$formatter = $this->getFormatter();
|
|
|
|
[ $actualMessage, $actualContext ] = $formatter->getPsr3MessageAndContext( $status );
|
|
$this->assertSame( $expectedMessage, $actualMessage );
|
|
$this->assertSame( $expectedContext, $actualContext );
|
|
}
|
|
|
|
public static function provideGetPsr3MessageAndContext() {
|
|
return [
|
|
// parameters to StatusValue::error() calls as array of arrays; expected message; expected context
|
|
'no errors' => [
|
|
[],
|
|
"Internal error: MediaWiki\Status\StatusFormatter::getWikiText called for a good result, this is incorrect\n",
|
|
[],
|
|
],
|
|
// make sure that the rawmessage_2 hack works as the following tests rely on it
|
|
'rawmessage_2' => [
|
|
[ [ 'rawmessage_2', 'foo' ] ],
|
|
'{parameter1}',
|
|
[ 'parameter1' => 'foo' ],
|
|
],
|
|
'two errors' => [
|
|
[ [ 'rawmessage_2', 'foo' ], [ 'rawmessage_2', 'bar' ] ],
|
|
"* foo\n* bar\n",
|
|
[],
|
|
],
|
|
'unknown subclass' => [
|
|
// phpcs:ignore Squiz.WhiteSpace.ScopeClosingBrace.ContentBefore
|
|
[ [ new class( 'rawmessage_2', [ 'foo' ] ) extends Message {} ] ],
|
|
'foo',
|
|
[],
|
|
],
|
|
'non-scalar parameter' => [
|
|
[ [ new Message( 'rawmessage_2', [ new Message( 'rawmessage_2', [ 'foo' ] ) ] ) ] ],
|
|
'foo',
|
|
[],
|
|
],
|
|
'one parameter' => [
|
|
[ [ 'apiwarn-invalidtitle', 'foo' ] ],
|
|
'"{parameter1}" is not a valid title.',
|
|
[ 'parameter1' => 'foo' ],
|
|
],
|
|
'multiple parameters' => [
|
|
[ [ 'api-exception-trace', 'foo', 'bar', 'baz', 'boom' ] ],
|
|
"{parameter1} at {parameter2}({parameter3})\n{parameter4}",
|
|
[ 'parameter1' => 'foo', 'parameter2' => 'bar', 'parameter3' => 'baz', 'parameter4' => 'boom' ],
|
|
],
|
|
'formatted parameter' => [
|
|
[ [ 'apiwarn-invalidtitle', Message::numParam( 1000000 ) ] ],
|
|
'"{parameter1}" is not a valid title.',
|
|
[ 'parameter1' => 1000000 ],
|
|
],
|
|
'rawmessage' => [
|
|
[ [ 'rawmessage', 'foo' ] ],
|
|
'foo',
|
|
[],
|
|
],
|
|
'RawMessage' => [
|
|
[ [ new RawMessage( 'foo $1 baz', [ 'bar' ] ) ] ],
|
|
'foo {parameter1} baz',
|
|
[ 'parameter1' => 'bar' ],
|
|
],
|
|
];
|
|
}
|
|
|
|
public function testGetErrorMessage() {
|
|
$formatter = $this->getFormatter();
|
|
$formatter = TestingAccessWrapper::newFromObject( $formatter );
|
|
$key = 'foo';
|
|
$params = [ 'bar' ];
|
|
|
|
/** @var Message $message */
|
|
$message = $formatter->getErrorMessage( array_merge( [ $key ], $params ) );
|
|
$this->assertInstanceOf( Message::class, $message );
|
|
$this->assertEquals( $key, $message->getKey() );
|
|
$this->assertEquals( $params, $message->getParams() );
|
|
}
|
|
|
|
public function testGetErrorMessageComplexParam() {
|
|
$formatter = $this->getFormatter();
|
|
$formatter = TestingAccessWrapper::newFromObject( $formatter );
|
|
$key = 'foo';
|
|
$params = [ 'bar', Message::numParam( 5 ) ];
|
|
|
|
/** @var Message $message */
|
|
$message = $formatter->getErrorMessage( array_merge( [ $key ], $params ) );
|
|
$this->assertInstanceOf( Message::class, $message );
|
|
$this->assertEquals( $key, $message->getKey() );
|
|
$this->assertEquals( $params, $message->getParams() );
|
|
}
|
|
|
|
public function testGetErrorMessageArray() {
|
|
$formatter = $this->getFormatter();
|
|
$formatter = TestingAccessWrapper::newFromObject( $formatter );
|
|
$key = 'foo';
|
|
$params = [ 'bar' ];
|
|
|
|
/** @var Message[] $messageArray */
|
|
$messageArray = $formatter->getErrorMessageArray(
|
|
[
|
|
array_merge( [ $key ], $params ),
|
|
array_merge( [ $key ], $params )
|
|
]
|
|
);
|
|
|
|
$this->assertIsArray( $messageArray );
|
|
$this->assertCount( 2, $messageArray );
|
|
foreach ( $messageArray as $message ) {
|
|
$this->assertInstanceOf( Message::class, $message );
|
|
$this->assertEquals( $key, $message->getKey() );
|
|
$this->assertEquals( $params, $message->getParams() );
|
|
}
|
|
}
|
|
|
|
}
|