3 namespace MediaWiki\Tests\SpecialPage
;
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
;
18 use Wikimedia\ScopedCallback
;
19 use Wikimedia\TestingAccessWrapper
;
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
42 class SpecialPageFactoryTest
extends MediaWikiIntegrationTestCase
{
43 private function getFactory() {
44 return $this->getServiceContainer()->getSpecialPageFactory();
47 public function testHookNotCalledTwice() {
49 $this->setTemporaryHook(
50 'SpecialPage_initList',
51 static function () use ( &$count ) {
55 $spf = $this->getServiceContainer()->getSpecialPageFactory();
58 $this->assertSame( 1, $count );
61 public function newSpecialAllPages() {
62 return new SpecialAllPages();
65 public function specialPageProvider() {
66 $specialPageTestHelper = new SpecialPageTestHelper();
69 'class name' => [ 'SpecialAllPages', false ],
70 'closure' => [ static function () {
71 return new SpecialAllPages();
73 'function' => [ [ $this, 'newSpecialAllPages' ], false ],
74 'callback string' => [ SpecialPageTestHelper
::class . '::newSpecialAllPages', false ],
75 'callback with object' => [
76 [ $specialPageTestHelper, 'newSpecialAllPages' ],
80 [ SpecialPageTestHelper
::class, 'newSpecialAllPages' ],
83 'object factory spec' => [
84 [ 'class' => SpecialAllPages
::class ],
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 ) )
193 $this->setContentLang( $lang );
195 // Catch the warnings we expect to be raised
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;
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() {
237 'Canonical name wins',
238 [ 'Foo' => [ 'Foo', 'Bar' ], 'Baz' => [ 'Foo', 'BazPage', 'Baz2' ] ],
246 'Doesn\'t redirect to a different special page\'s canonical name',
247 [ 'Foo' => [ 'Foo', 'Bar' ], 'Baz' => [ 'Foo', 'BazPage', 'Baz2' ] ],
255 'Canonical name wins even if not aliased',
256 [ 'Foo' => [ 'FooPage' ], 'Baz' => [ 'Foo', 'BazPage', 'Baz2' ] ],
264 'Doesn\'t redirect to a different special page\'s canonical name even if not aliased',
265 [ 'Foo' => [ 'FooPage' ], 'Baz' => [ 'Foo', 'BazPage', 'Baz2' ] ],
273 'First local name beats non-first',
274 [ 'First' => [ 'Foo' ], 'NonFirst' => [ 'Bar', 'Foo' ] ],
282 'Doesn\'t redirect to a different special page\'s first alias',
285 'First' => [ 'Bar' ],
286 'Baz' => [ 'Foo', 'Bar', 'BazPage', 'Baz2' ]
295 'Doesn\'t redirect wrong even if all aliases conflict',
298 'First' => [ 'Bar' ],
299 'Baz' => [ 'Foo', 'Bar' ]
310 public function testGetAliasListRecursion() {
312 $this->setTemporaryHook(
313 'SpecialPage_initList',
314 function () use ( &$called ) {
315 $this->getServiceContainer()
316 ->getSpecialPageFactory()
317 ->getLocalNameFor( 'Specialpages' );
321 $this->getFactory()->getLocalNameFor( 'Specialpages' );
322 $this->assertTrue( $called, 'Recursive call succeeded' );
326 * @covers \MediaWiki\SpecialPage\SpecialPageFactory::getPage
328 public function testSpecialPageCreationThatRequiresService() {
331 $this->overrideConfigValue( MainConfigNames
::SpecialPages
,
333 'factory' => static function ( $spf ) use ( &$type ) {
334 $type = get_class( $spf );
336 return new class() extends SpecialPage
{
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
, [
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() {
379 $factory = $this->getFactory();
380 $factory->getPage( 'ExceptionPage' );
382 $this->expectExceptionObject( $expectedException );
383 $factory->capturePath(
384 Title
::makeTitle( NS_SPECIAL
, 'ExceptionPage' ),
385 RequestContext
::getMain()