Localisation updates from https://translatewiki.net.
[mediawiki.git] / tests / phpunit / includes / specialpage / SpecialPageFactoryTest.php
blobd531ae19ef6b3105b184318e157cea4349b902e9
1 <?php
3 namespace MediaWiki\Tests\SpecialPage;
5 use Exception;
6 use MediaWiki\Context\RequestContext;
7 use MediaWiki\MainConfigNames;
8 use MediaWiki\MainConfigSchema;
9 use MediaWiki\Output\OutputPage;
10 use MediaWiki\Page\PageReferenceValue;
11 use MediaWiki\Request\FauxRequest;
12 use MediaWiki\SpecialPage\SpecialPage;
13 use MediaWiki\SpecialPage\SpecialPageFactory;
14 use MediaWiki\Specials\SpecialAllPages;
15 use MediaWiki\Title\Title;
16 use MediaWikiIntegrationTestCase;
17 use RuntimeException;
18 use Wikimedia\ScopedCallback;
19 use Wikimedia\TestingAccessWrapper;
21 /**
22 * Factory for handling the special page list and generating SpecialPage objects.
24 * This program is free software; you can redistribute it and/or modify
25 * it under the terms of the GNU General Public License as published by
26 * the Free Software Foundation; either version 2 of the License, or
27 * (at your option) any later version.
29 * This program is distributed in the hope that it will be useful,
30 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 * GNU General Public License for more details.
34 * You should have received a copy of the GNU General Public License along
35 * with this program; if not, write to the Free Software Foundation, Inc.,
36 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
37 * http://www.gnu.org/copyleft/gpl.html
39 * @covers \MediaWiki\SpecialPage\SpecialPageFactory
40 * @group SpecialPage
42 class SpecialPageFactoryTest extends MediaWikiIntegrationTestCase {
43 private function getFactory() {
44 return $this->getServiceContainer()->getSpecialPageFactory();
47 public function testHookNotCalledTwice() {
48 $count = 0;
49 $this->setTemporaryHook(
50 'SpecialPage_initList',
51 static function () use ( &$count ) {
52 $count++;
55 $spf = $this->getServiceContainer()->getSpecialPageFactory();
56 $spf->getNames();
57 $spf->getNames();
58 $this->assertSame( 1, $count );
61 public function newSpecialAllPages() {
62 return new SpecialAllPages();
65 public function specialPageProvider() {
66 $specialPageTestHelper = new SpecialPageTestHelper();
68 return [
69 'class name' => [ 'SpecialAllPages', false ],
70 'closure' => [ static function () {
71 return new SpecialAllPages();
72 }, false ],
73 'function' => [ [ $this, 'newSpecialAllPages' ], false ],
74 'callback string' => [ SpecialPageTestHelper::class . '::newSpecialAllPages', false ],
75 'callback with object' => [
76 [ $specialPageTestHelper, 'newSpecialAllPages' ],
77 false
79 'callback array' => [
80 [ SpecialPageTestHelper::class, 'newSpecialAllPages' ],
81 false
83 'object factory spec' => [
84 [ 'class' => SpecialAllPages::class ],
85 false
90 /**
91 * @covers \MediaWiki\SpecialPage\SpecialPageFactory::getPage
92 * @dataProvider specialPageProvider
94 public function testGetPage( $spec, $shouldReuseInstance ) {
95 $this->overrideConfigValue(
96 MainConfigNames::SpecialPages,
97 [ 'testdummy' => $spec ] + MainConfigSchema::getDefaultValue( MainConfigNames::SpecialPages )
100 $factory = $this->getFactory();
101 $page = $factory->getPage( 'testdummy' );
102 $this->assertInstanceOf( SpecialPage::class, $page );
104 $page2 = $factory->getPage( 'testdummy' );
105 $this->assertEquals( $shouldReuseInstance, $page2 === $page, "Should re-use instance:" );
109 * @covers \MediaWiki\SpecialPage\SpecialPageFactory::getNames
111 public function testGetNames() {
112 $this->overrideConfigValue(
113 MainConfigNames::SpecialPages,
114 [ 'testdummy' => SpecialAllPages::class ] +
115 MainConfigSchema::getDefaultValue( MainConfigNames::SpecialPages )
118 $names = $this->getFactory()->getNames();
119 $this->assertIsArray( $names );
120 $this->assertContains( 'testdummy', $names );
124 * @covers \MediaWiki\SpecialPage\SpecialPageFactory::resolveAlias
126 public function testResolveAlias() {
127 $this->overrideConfigValue( MainConfigNames::LanguageCode, 'de' );
129 [ $name, $param ] = $this->getFactory()->resolveAlias( 'Spezialseiten/Foo' );
130 $this->assertEquals( 'Specialpages', $name );
131 $this->assertEquals( 'Foo', $param );
135 * @covers \MediaWiki\SpecialPage\SpecialPageFactory::getLocalNameFor
137 public function testGetLocalNameFor() {
138 $this->overrideConfigValue( MainConfigNames::LanguageCode, 'de' );
140 $name = $this->getFactory()->getLocalNameFor( 'Specialpages', 'Foo' );
141 $this->assertEquals( 'Spezialseiten/Foo', $name );
145 * @covers \MediaWiki\SpecialPage\SpecialPageFactory::getTitleForAlias
147 public function testGetTitleForAlias() {
148 $this->overrideConfigValue( MainConfigNames::LanguageCode, 'de' );
150 $title = $this->getFactory()->getTitleForAlias( 'Specialpages/Foo' );
151 $this->assertEquals( 'Spezialseiten/Foo', $title->getText() );
152 $this->assertEquals( NS_SPECIAL, $title->getNamespace() );
155 public static function provideExecutePath() {
156 yield [ 'BlankPage', 'intentionallyblankpage' ];
158 $path = new PageReferenceValue( NS_SPECIAL, 'BlankPage', PageReferenceValue::LOCAL );
159 yield [ $path, 'intentionallyblankpage' ];
163 * @dataProvider provideExecutePath
164 * @covers \MediaWiki\SpecialPage\SpecialPageFactory::executePAth
166 public function testExecutePath( $path, $expected ) {
167 $this->overrideConfigValue( MainConfigNames::LanguageCode, 'qqx' );
169 $context = new RequestContext();
170 $context->setRequest( new FauxRequest() );
172 $output = new OutputPage( $context );
173 $context->setOutput( $output );
175 $this->getFactory()->executePath( $path, $context );
176 $this->assertStringContainsString( $expected, $output->getHTML() );
180 * @dataProvider provideTestConflictResolution
182 public function testConflictResolution(
183 $test, $aliasesList, $alias, $expectedName, $expectedAlias, $expectWarnings
185 $lang = clone $this->getServiceContainer()->getContentLanguage();
186 $wrappedLang = TestingAccessWrapper::newFromObject( $lang );
187 $wrappedLang->mExtendedSpecialPageAliases = $aliasesList;
188 $this->overrideConfigValues( [
189 MainConfigNames::DevelopmentWarnings => true,
190 MainConfigNames::SpecialPages =>
191 array_combine( array_keys( $aliasesList ), array_keys( $aliasesList ) )
192 ] );
193 $this->setContentLang( $lang );
195 // Catch the warnings we expect to be raised
196 $warnings = [];
197 set_error_handler( static function ( $errno, $errstr ) use ( &$warnings ) {
198 if ( preg_match( '/First alias \'[^\']*\' for .*/', $errstr ) ||
199 preg_match( '/Did not find a usable alias for special page .*/', $errstr )
201 $warnings[] = $errstr;
202 return true;
204 return false;
205 } );
206 $reset = new ScopedCallback( 'restore_error_handler' );
208 [ $name, /*...*/ ] = $this->getFactory()->resolveAlias( $alias );
209 $this->assertEquals( $expectedName, $name, "$test: Alias to name" );
210 $result = $this->getFactory()->getLocalNameFor( $name );
211 $this->assertEquals( $expectedAlias, $result, "$test: Alias to name to alias" );
213 $gotWarnings = count( $warnings );
214 if ( $gotWarnings !== $expectWarnings ) {
215 $this->fail( "Expected $expectWarnings warning(s), but got $gotWarnings:\n" .
216 implode( "\n", $warnings )
222 * @dataProvider provideTestConflictResolution
224 public function testConflictResolutionReversed(
225 $test, $aliasesList, $alias, $expectedName, $expectedAlias, $expectWarnings
227 // Make sure order doesn't matter by reversing the list
228 $aliasesList = array_reverse( $aliasesList );
229 $this->testConflictResolution(
230 $test, $aliasesList, $alias, $expectedName, $expectedAlias, $expectWarnings
234 public static function provideTestConflictResolution() {
235 return [
237 'Canonical name wins',
238 [ 'Foo' => [ 'Foo', 'Bar' ], 'Baz' => [ 'Foo', 'BazPage', 'Baz2' ] ],
239 'Foo',
240 'Foo',
241 'Foo',
246 'Doesn\'t redirect to a different special page\'s canonical name',
247 [ 'Foo' => [ 'Foo', 'Bar' ], 'Baz' => [ 'Foo', 'BazPage', 'Baz2' ] ],
248 'Baz',
249 'Baz',
250 'BazPage',
255 'Canonical name wins even if not aliased',
256 [ 'Foo' => [ 'FooPage' ], 'Baz' => [ 'Foo', 'BazPage', 'Baz2' ] ],
257 'Foo',
258 'Foo',
259 'FooPage',
264 'Doesn\'t redirect to a different special page\'s canonical name even if not aliased',
265 [ 'Foo' => [ 'FooPage' ], 'Baz' => [ 'Foo', 'BazPage', 'Baz2' ] ],
266 'Baz',
267 'Baz',
268 'BazPage',
273 'First local name beats non-first',
274 [ 'First' => [ 'Foo' ], 'NonFirst' => [ 'Bar', 'Foo' ] ],
275 'Foo',
276 'First',
277 'Foo',
282 'Doesn\'t redirect to a different special page\'s first alias',
284 'Foo' => [ 'Foo' ],
285 'First' => [ 'Bar' ],
286 'Baz' => [ 'Foo', 'Bar', 'BazPage', 'Baz2' ]
288 'Baz',
289 'Baz',
290 'BazPage',
295 'Doesn\'t redirect wrong even if all aliases conflict',
297 'Foo' => [ 'Foo' ],
298 'First' => [ 'Bar' ],
299 'Baz' => [ 'Foo', 'Bar' ]
301 'Baz',
302 'Baz',
303 'Baz',
310 public function testGetAliasListRecursion() {
311 $called = false;
312 $this->setTemporaryHook(
313 'SpecialPage_initList',
314 function () use ( &$called ) {
315 $this->getServiceContainer()
316 ->getSpecialPageFactory()
317 ->getLocalNameFor( 'Specialpages' );
318 $called = true;
321 $this->getFactory()->getLocalNameFor( 'Specialpages' );
322 $this->assertTrue( $called, 'Recursive call succeeded' );
326 * @covers \MediaWiki\SpecialPage\SpecialPageFactory::getPage
328 public function testSpecialPageCreationThatRequiresService() {
329 $type = null;
331 $this->overrideConfigValue( MainConfigNames::SpecialPages,
332 [ 'TestPage' => [
333 'factory' => static function ( $spf ) use ( &$type ) {
334 $type = get_class( $spf );
336 return new class() extends SpecialPage {
340 'services' => [
341 'SpecialPageFactory'
346 $this->getFactory()->getPage( 'TestPage' );
348 $this->assertEquals( SpecialPageFactory::class, $type );
352 * @covers \MediaWiki\SpecialPage\SpecialPageFactory::capturePath
354 public function testSpecialPageCapturePathExceptions() {
355 $expectedException = new RuntimeException( 'Uh-oh!' );
356 $this->overrideConfigValue( MainConfigNames::SpecialPages, [
357 'ExceptionPage' => [
358 'factory' => static function () use ( $expectedException ) {
359 return new class( $expectedException ) extends SpecialPage {
360 private Exception $expectedException;
362 public function __construct( $expectedException ) {
363 parent::__construct();
364 $this->expectedException = $expectedException;
367 public function execute( $par ) {
368 throw $this->expectedException;
371 public function isIncludable() {
372 return true;
377 ] );
379 $factory = $this->getFactory();
380 $factory->getPage( 'ExceptionPage' );
382 $this->expectExceptionObject( $expectedException );
383 $factory->capturePath(
384 Title::makeTitle( NS_SPECIAL, 'ExceptionPage' ),
385 RequestContext::getMain()