X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=blobdiff_plain;f=tests%2Fphpunit%2Fincludes%2Ftitle%2FNamespaceInfoTest.php;h=556c640bd6bb5448787b01e787316715c89df8ef;hp=cc7df8d31f48d604cbb776d85c81eb7a4836e600;hb=a3cd158d8433e41cbeb299f0e268dfef363b2afd;hpb=3353ced6cd3148de1549568ee9633a913fd5faab diff --git a/tests/phpunit/includes/title/NamespaceInfoTest.php b/tests/phpunit/includes/title/NamespaceInfoTest.php index cc7df8d31f..556c640bd6 100644 --- a/tests/phpunit/includes/title/NamespaceInfoTest.php +++ b/tests/phpunit/includes/title/NamespaceInfoTest.php @@ -5,218 +5,246 @@ * @file */ -use MediaWiki\MediaWikiServices; +use MediaWiki\Config\ServiceOptions; class NamespaceInfoTest extends MediaWikiTestCase { + /********************************************************************************************** + * Shared code + * %{ + */ + private $scopedCallback; - /** @var NamespaceInfo */ - private $obj; - - protected function setUp() { + public function setUp() { parent::setUp(); - $this->setMwGlobals( [ - 'wgContentNamespaces' => [ NS_MAIN ], - 'wgNamespacesWithSubpages' => [ - NS_TALK => true, - NS_USER => true, - NS_USER_TALK => true, - ], - 'wgCapitalLinks' => true, - 'wgCapitalLinkOverrides' => [], - 'wgNonincludableNamespaces' => [], - ] ); - - $this->obj = MediaWikiServices::getInstance()->getNamespaceInfo(); - } + // Boo, there's still some global state in the class :( + global $wgHooks; + $hooks = $wgHooks; + unset( $hooks['CanonicalNamespaces'] ); + $this->setMwGlobals( 'wgHooks', $hooks ); - /** - * @todo Write more texts, handle $wgAllowImageMoving setting - * @covers NamespaceInfo::isMovable - */ - public function testIsMovable() { - $this->assertFalse( $this->obj->isMovable( NS_SPECIAL ) ); + $this->scopedCallback = + ExtensionRegistry::getInstance()->setAttributeForTest( 'ExtensionNamespaces', [] ); } - private function assertIsSubject( $ns ) { - $this->assertTrue( $this->obj->isSubject( $ns ) ); - } + public function tearDown() { + $this->scopedCallback = null; - private function assertIsNotSubject( $ns ) { - $this->assertFalse( $this->obj->isSubject( $ns ) ); + parent::tearDown(); } /** - * Please make sure to change testIsTalk() if you change the assertions below - * @covers NamespaceInfo::isSubject + * TODO Make this a const once HHVM support is dropped (T192166) */ - public function testIsSubject() { - // Special namespaces - $this->assertIsSubject( NS_MEDIA ); - $this->assertIsSubject( NS_SPECIAL ); - - // Subject pages - $this->assertIsSubject( NS_MAIN ); - $this->assertIsSubject( NS_USER ); - $this->assertIsSubject( 100 ); # user defined - - // Talk pages - $this->assertIsNotSubject( NS_TALK ); - $this->assertIsNotSubject( NS_USER_TALK ); - $this->assertIsNotSubject( 101 ); # user defined + private static $defaultOptions = [ + 'AllowImageMoving' => true, + 'CanonicalNamespaceNames' => [ + NS_TALK => 'Talk', + NS_USER => 'User', + NS_USER_TALK => 'User_talk', + NS_SPECIAL => 'Special', + NS_MEDIA => 'Media', + ], + 'CapitalLinkOverrides' => [], + 'CapitalLinks' => true, + 'ContentNamespaces' => [ NS_MAIN ], + 'ExtraNamespaces' => [], + 'ExtraSignatureNamespaces' => [], + 'NamespaceContentModels' => [], + 'NamespaceProtection' => [], + 'NamespacesWithSubpages' => [ + NS_TALK => true, + NS_USER => true, + NS_USER_TALK => true, + ], + 'NonincludableNamespaces' => [], + 'RestrictionLevels' => [ '', 'autoconfirmed', 'sysop' ], + ]; + + private function newObj( array $options = [] ) : NamespaceInfo { + return new NamespaceInfo( new ServiceOptions( NamespaceInfo::$constructorOptions, + $options, self::$defaultOptions ) ); } - private function assertIsTalk( $ns ) { - $this->assertTrue( $this->obj->isTalk( $ns ) ); - } + // %} End shared code - private function assertIsNotTalk( $ns ) { - $this->assertFalse( $this->obj->isTalk( $ns ) ); - } + /********************************************************************************************** + * Basic methods + * %{ + */ /** - * Reverse of testIsSubject(). - * Please update testIsSubject() if you change assertions below - * @covers NamespaceInfo::isTalk + * @covers NamespaceInfo::__construct + * @dataProvider provideConstructor + * @param ServiceOptions $options + * @param string|null $expectedExceptionText */ - public function testIsTalk() { - // Special namespaces - $this->assertIsNotTalk( NS_MEDIA ); - $this->assertIsNotTalk( NS_SPECIAL ); - - // Subject pages - $this->assertIsNotTalk( NS_MAIN ); - $this->assertIsNotTalk( NS_USER ); - $this->assertIsNotTalk( 100 ); # user defined + public function testConstructor( ServiceOptions $options, $expectedExceptionText = null ) { + if ( $expectedExceptionText !== null ) { + $this->setExpectedException( \Wikimedia\Assert\PreconditionException::class, + $expectedExceptionText ); + } + new NamespaceInfo( $options ); + $this->assertTrue( true ); + } - // Talk pages - $this->assertIsTalk( NS_TALK ); - $this->assertIsTalk( NS_USER_TALK ); - $this->assertIsTalk( 101 ); # user defined + public function provideConstructor() { + return [ + [ new ServiceOptions( NamespaceInfo::$constructorOptions, self::$defaultOptions ) ], + [ new ServiceOptions( [], [] ), 'Required options missing: ' ], + [ new ServiceOptions( + array_merge( NamespaceInfo::$constructorOptions, [ 'invalid' ] ), + self::$defaultOptions, + [ 'invalid' => '' ] + ), 'Unsupported options passed: invalid' ], + ]; } /** - * @covers NamespaceInfo::getSubject + * @dataProvider provideIsMovable + * @covers NamespaceInfo::isMovable + * + * @param bool $expected + * @param int $ns + * @param bool $allowImageMoving */ - public function testGetSubject() { - // Special namespaces are their own subjects - $this->assertEquals( NS_MEDIA, $this->obj->getSubject( NS_MEDIA ) ); - $this->assertEquals( NS_SPECIAL, $this->obj->getSubject( NS_SPECIAL ) ); - - $this->assertEquals( NS_MAIN, $this->obj->getSubject( NS_TALK ) ); - $this->assertEquals( NS_USER, $this->obj->getSubject( NS_USER_TALK ) ); + public function testIsMovable( $expected, $ns, $allowImageMoving = true ) { + $obj = $this->newObj( [ 'AllowImageMoving' => $allowImageMoving ] ); + $this->assertSame( $expected, $obj->isMovable( $ns ) ); } - /** - * Regular getTalk() calls - * Namespaces without a talk page (NS_MEDIA, NS_SPECIAL) are tested in - * the function testGetTalkExceptions() - * @covers NamespaceInfo::getTalk - */ - public function testGetTalk() { - $this->assertEquals( NS_TALK, $this->obj->getTalk( NS_MAIN ) ); - $this->assertEquals( NS_TALK, $this->obj->getTalk( NS_TALK ) ); - $this->assertEquals( NS_USER_TALK, $this->obj->getTalk( NS_USER ) ); - $this->assertEquals( NS_USER_TALK, $this->obj->getTalk( NS_USER_TALK ) ); + public function provideIsMovable() { + return [ + 'Main' => [ true, NS_MAIN ], + 'Talk' => [ true, NS_TALK ], + 'Special' => [ false, NS_SPECIAL ], + 'Nonexistent even namespace' => [ true, 1234 ], + 'Nonexistent odd namespace' => [ true, 12345 ], + + 'Media with image moving' => [ false, NS_MEDIA, true ], + 'Media with no image moving' => [ false, NS_MEDIA, false ], + 'File with image moving' => [ true, NS_FILE, true ], + 'File with no image moving' => [ false, NS_FILE, false ], + ]; } /** - * Exceptions with getTalk() - * NS_MEDIA does not have talk pages. MediaWiki raise an exception for them. - * @expectedException MWException - * @covers NamespaceInfo::getTalk + * @param int $ns + * @param bool $expected + * @dataProvider provideIsSubject + * @covers NamespaceInfo::isSubject */ - public function testGetTalkExceptionsForNsMedia() { - $this->assertNull( $this->obj->getTalk( NS_MEDIA ) ); + public function testIsSubject( $ns, $expected ) { + $this->assertSame( $expected, $this->newObj()->isSubject( $ns ) ); } /** - * Exceptions with getTalk() - * NS_SPECIAL does not have talk pages. MediaWiki raise an exception for them. - * @expectedException MWException - * @covers NamespaceInfo::getTalk + * @param int $ns + * @param bool $expected + * @dataProvider provideIsSubject + * @covers NamespaceInfo::isTalk */ - public function testGetTalkExceptionsForNsSpecial() { - $this->assertNull( $this->obj->getTalk( NS_SPECIAL ) ); + public function testIsTalk( $ns, $expected ) { + $this->assertSame( !$expected, $this->newObj()->isTalk( $ns ) ); } - /** - * Regular getAssociated() calls - * Namespaces without an associated page (NS_MEDIA, NS_SPECIAL) are tested in - * the function testGetAssociatedExceptions() - * @covers NamespaceInfo::getAssociated - */ - public function testGetAssociated() { - $this->assertEquals( NS_TALK, $this->obj->getAssociated( NS_MAIN ) ); - $this->assertEquals( NS_MAIN, $this->obj->getAssociated( NS_TALK ) ); + public function provideIsSubject() { + return [ + // Special namespaces + [ NS_MEDIA, true ], + [ NS_SPECIAL, true ], + + // Subject pages + [ NS_MAIN, true ], + [ NS_USER, true ], + [ 100, true ], + + // Talk pages + [ NS_TALK, false ], + [ NS_USER_TALK, false ], + [ 101, false ], + ]; } - # ## Exceptions with getAssociated() - # ## NS_MEDIA and NS_SPECIAL do not have talk pages. MediaWiki raises - # ## an exception for them. /** - * @expectedException MWException - * @covers NamespaceInfo::getAssociated + * @covers NamespaceInfo::exists + * @dataProvider provideExists + * @param int $ns + * @param bool $expected */ - public function testGetAssociatedExceptionsForNsMedia() { - $this->assertNull( $this->obj->getAssociated( NS_MEDIA ) ); + public function testExists( $ns, $expected ) { + $this->assertSame( $expected, $this->newObj()->exists( $ns ) ); } - /** - * @expectedException MWException - * @covers NamespaceInfo::getAssociated - */ - public function testGetAssociatedExceptionsForNsSpecial() { - $this->assertNull( $this->obj->getAssociated( NS_SPECIAL ) ); + public function provideExists() { + return [ + 'Main' => [ NS_MAIN, true ], + 'Talk' => [ NS_TALK, true ], + 'Media' => [ NS_MEDIA, true ], + 'Special' => [ NS_SPECIAL, true ], + 'Nonexistent' => [ 12345, false ], + 'Negative nonexistent' => [ -12345, false ], + ]; } /** * Note if we add a namespace registration system with keys like 'MAIN' - * we should add tests here for equivilance on things like 'MAIN' == 0 + * we should add tests here for equivalence on things like 'MAIN' == 0 * and 'MAIN' == NS_MAIN. * @covers NamespaceInfo::equals */ public function testEquals() { - $this->assertTrue( $this->obj->equals( NS_MAIN, NS_MAIN ) ); - $this->assertTrue( $this->obj->equals( NS_MAIN, 0 ) ); // In case we make NS_MAIN 'MAIN' - $this->assertTrue( $this->obj->equals( NS_USER, NS_USER ) ); - $this->assertTrue( $this->obj->equals( NS_USER, 2 ) ); - $this->assertTrue( $this->obj->equals( NS_USER_TALK, NS_USER_TALK ) ); - $this->assertTrue( $this->obj->equals( NS_SPECIAL, NS_SPECIAL ) ); - $this->assertFalse( $this->obj->equals( NS_MAIN, NS_TALK ) ); - $this->assertFalse( $this->obj->equals( NS_USER, NS_USER_TALK ) ); - $this->assertFalse( $this->obj->equals( NS_PROJECT, NS_TEMPLATE ) ); + $obj = $this->newObj(); + $this->assertTrue( $obj->equals( NS_MAIN, NS_MAIN ) ); + $this->assertTrue( $obj->equals( NS_MAIN, 0 ) ); // In case we make NS_MAIN 'MAIN' + $this->assertTrue( $obj->equals( NS_USER, NS_USER ) ); + $this->assertTrue( $obj->equals( NS_USER, 2 ) ); + $this->assertTrue( $obj->equals( NS_USER_TALK, NS_USER_TALK ) ); + $this->assertTrue( $obj->equals( NS_SPECIAL, NS_SPECIAL ) ); + $this->assertFalse( $obj->equals( NS_MAIN, NS_TALK ) ); + $this->assertFalse( $obj->equals( NS_USER, NS_USER_TALK ) ); + $this->assertFalse( $obj->equals( NS_PROJECT, NS_TEMPLATE ) ); } /** + * @param int $ns1 + * @param int $ns2 + * @param bool $expected + * @dataProvider provideSubjectEquals * @covers NamespaceInfo::subjectEquals */ - public function testSubjectEquals() { - $this->assertSameSubject( NS_MAIN, NS_MAIN ); - $this->assertSameSubject( NS_MAIN, 0 ); // In case we make NS_MAIN 'MAIN' - $this->assertSameSubject( NS_USER, NS_USER ); - $this->assertSameSubject( NS_USER, 2 ); - $this->assertSameSubject( NS_USER_TALK, NS_USER_TALK ); - $this->assertSameSubject( NS_SPECIAL, NS_SPECIAL ); - $this->assertSameSubject( NS_MAIN, NS_TALK ); - $this->assertSameSubject( NS_USER, NS_USER_TALK ); + public function testSubjectEquals( $ns1, $ns2, $expected ) { + $this->assertSame( $expected, $this->newObj()->subjectEquals( $ns1, $ns2 ) ); + } - $this->assertDifferentSubject( NS_PROJECT, NS_TEMPLATE ); - $this->assertDifferentSubject( NS_SPECIAL, NS_MAIN ); + public function provideSubjectEquals() { + return [ + [ NS_MAIN, NS_MAIN, true ], + // In case we make NS_MAIN 'MAIN' + [ NS_MAIN, 0, true ], + [ NS_USER, NS_USER, true ], + [ NS_USER, 2, true ], + [ NS_USER_TALK, NS_USER_TALK, true ], + [ NS_SPECIAL, NS_SPECIAL, true ], + [ NS_MAIN, NS_TALK, true ], + [ NS_USER, NS_USER_TALK, true ], + + [ NS_PROJECT, NS_TEMPLATE, false ], + [ NS_SPECIAL, NS_MAIN, false ], + [ NS_MEDIA, NS_SPECIAL, false ], + [ NS_SPECIAL, NS_MEDIA, false ], + ]; } /** - * @covers NamespaceInfo::subjectEquals + * @dataProvider provideHasTalkNamespace + * @covers NamespaceInfo::hasTalkNamespace + * + * @param int $ns + * @param bool $expected */ - public function testSpecialAndMediaAreDifferentSubjects() { - $this->assertDifferentSubject( - NS_MEDIA, NS_SPECIAL, - "NS_MEDIA and NS_SPECIAL are different subject namespaces" - ); - $this->assertDifferentSubject( - NS_SPECIAL, NS_MEDIA, - "NS_SPECIAL and NS_MEDIA are different subject namespaces" - ); + public function testHasTalkNamespace( $ns, $expected ) { + $this->assertSame( $expected, $this->newObj()->hasTalkNamespace( $ns ) ); } public function provideHasTalkNamespace() { @@ -235,379 +263,1071 @@ class NamespaceInfoTest extends MediaWikiTestCase { } /** - * @dataProvider provideHasTalkNamespace - * @covers NamespaceInfo::hasTalkNamespace + * @param int $ns + * @param bool $expected + * @param array $contentNamespaces + * @covers NamespaceInfo::isContent + * @dataProvider provideIsContent + */ + public function testIsContent( $ns, $expected, $contentNamespaces = [ NS_MAIN ] ) { + $obj = $this->newObj( [ 'ContentNamespaces' => $contentNamespaces ] ); + $this->assertSame( $expected, $obj->isContent( $ns ) ); + } + + public function provideIsContent() { + return [ + [ NS_MAIN, true ], + [ NS_MEDIA, false ], + [ NS_SPECIAL, false ], + [ NS_TALK, false ], + [ NS_USER, false ], + [ NS_CATEGORY, false ], + [ 100, false ], + [ 100, true, [ NS_MAIN, 100, 252 ] ], + [ 252, true, [ NS_MAIN, 100, 252 ] ], + [ NS_MAIN, true, [ NS_MAIN, 100, 252 ] ], + // NS_MAIN is always content + [ NS_MAIN, true, [] ], + ]; + } + + /** + * @dataProvider provideWantSignatures + * @covers NamespaceInfo::wantSignatures * * @param int $index * @param bool $expected */ - public function testHasTalkNamespace( $index, $expected ) { - $actual = $this->obj->hasTalkNamespace( $index ); - $this->assertSame( $actual, $expected, "NS $index" ); + public function testWantSignatures( $index, $expected ) { + $this->assertSame( $expected, $this->newObj()->wantSignatures( $index ) ); + } + + public function provideWantSignatures() { + return [ + 'Main' => [ NS_MAIN, false ], + 'Talk' => [ NS_TALK, true ], + 'User' => [ NS_USER, false ], + 'User talk' => [ NS_USER_TALK, true ], + 'Special' => [ NS_SPECIAL, false ], + 'Media' => [ NS_MEDIA, false ], + 'Nonexistent talk' => [ 12345, true ], + 'Nonexistent subject' => [ 123456, false ], + 'Nonexistent negative odd' => [ -12345, false ], + ]; } /** - * @dataProvider provideHasTalkNamespace - * @covers MWNamespace::canTalk + * @dataProvider provideWantSignatures_ExtraSignatureNamespaces + * @covers NamespaceInfo::wantSignatures * * @param int $index + * @param int $expected + */ + public function testWantSignatures_ExtraSignatureNamespaces( $index, $expected ) { + $obj = $this->newObj( [ 'ExtraSignatureNamespaces' => + [ NS_MAIN, NS_USER, NS_SPECIAL, NS_MEDIA, 123456, -12345 ] ] ); + $this->assertSame( $expected, $obj->wantSignatures( $index ) ); + } + + public function provideWantSignatures_ExtraSignatureNamespaces() { + $ret = array_map( + function ( $arr ) { + // We've added all these as extra signature namespaces, so expect true + return [ $arr[0], true ]; + }, + self::provideWantSignatures() + ); + + // Add one more that's false + $ret['Another nonexistent subject'] = [ 12345678, false ]; + return $ret; + } + + /** + * @param int $ns * @param bool $expected + * @covers NamespaceInfo::isWatchable + * @dataProvider provideIsWatchable */ - public function testCanTalk( $index, $expected ) { - $this->hideDeprecated( 'MWNamespace::canTalk' ); - $actual = MWNamespace::canTalk( $index ); - $this->assertSame( $actual, $expected, "NS $index" ); + public function testIsWatchable( $ns, $expected ) { + $this->assertSame( $expected, $this->newObj()->isWatchable( $ns ) ); } - private function assertIsContent( $ns ) { - $this->assertTrue( $this->obj->isContent( $ns ) ); + public function provideIsWatchable() { + return [ + // Specials namespaces are not watchable + [ NS_MEDIA, false ], + [ NS_SPECIAL, false ], + + // Core defined namespaces are watchables + [ NS_MAIN, true ], + [ NS_TALK, true ], + + // Additional, user defined namespaces are watchables + [ 100, true ], + [ 101, true ], + ]; + } + + /** + * @param int $ns + * @param int $expected + * @param array|null $namespacesWithSubpages To pass to constructor + * @covers NamespaceInfo::hasSubpages + * @dataProvider provideHasSubpages + */ + public function testHasSubpages( $ns, $expected, array $namespacesWithSubpages = null ) { + $obj = $this->newObj( $namespacesWithSubpages + ? [ 'NamespacesWithSubpages' => $namespacesWithSubpages ] + : [] ); + $this->assertSame( $expected, $obj->hasSubpages( $ns ) ); } - private function assertIsNotContent( $ns ) { - $this->assertFalse( $this->obj->isContent( $ns ) ); + public function provideHasSubpages() { + return [ + // Special namespaces: + [ NS_MEDIA, false ], + [ NS_SPECIAL, false ], + + // Namespaces without subpages + [ NS_MAIN, false ], + [ NS_MAIN, true, [ NS_MAIN => true ] ], + [ NS_MAIN, false, [ NS_MAIN => false ] ], + + // Some namespaces with subpages + [ NS_TALK, true ], + [ NS_USER, true ], + [ NS_USER_TALK, true ], + ]; } /** - * @covers NamespaceInfo::isContent + * @param $contentNamespaces To pass to constructor + * @param array $expected + * @dataProvider provideGetContentNamespaces + * @covers NamespaceInfo::getContentNamespaces */ - public function testIsContent() { - // NS_MAIN is a content namespace per DefaultSettings.php - // and per function definition. + public function testGetContentNamespaces( $contentNamespaces, array $expected ) { + $obj = $this->newObj( [ 'ContentNamespaces' => $contentNamespaces ] ); + $this->assertSame( $expected, $obj->getContentNamespaces() ); + } + + public function provideGetContentNamespaces() { + return [ + // Non-array + [ '', [ NS_MAIN ] ], + [ false, [ NS_MAIN ] ], + [ null, [ NS_MAIN ] ], + [ 5, [ NS_MAIN ] ], - $this->assertIsContent( NS_MAIN ); + // Empty array + [ [], [ NS_MAIN ] ], - // Other namespaces which are not expected to be content + // NS_MAIN is forced to be content even if unwanted + [ [ NS_USER, NS_CATEGORY ], [ NS_MAIN, NS_USER, NS_CATEGORY ] ], - $this->assertIsNotContent( NS_MEDIA ); - $this->assertIsNotContent( NS_SPECIAL ); - $this->assertIsNotContent( NS_TALK ); - $this->assertIsNotContent( NS_USER ); - $this->assertIsNotContent( NS_CATEGORY ); - $this->assertIsNotContent( 100 ); + // In other cases, return as-is + [ [ NS_MAIN ], [ NS_MAIN ] ], + [ [ NS_MAIN, NS_USER, NS_CATEGORY ], [ NS_MAIN, NS_USER, NS_CATEGORY ] ], + ]; } /** - * Similar to testIsContent() but alters the $wgContentNamespaces - * global variable. - * @covers NamespaceInfo::isContent + * @covers NamespaceInfo::getSubjectNamespaces */ - public function testIsContentAdvanced() { - global $wgContentNamespaces; + public function testGetSubjectNamespaces() { + $subjectsNS = $this->newObj()->getSubjectNamespaces(); + $this->assertContains( NS_MAIN, $subjectsNS, + "Talk namespaces should have NS_MAIN" ); + $this->assertNotContains( NS_TALK, $subjectsNS, + "Talk namespaces should have NS_TALK" ); - // Test that user defined namespace #252 is not content - $this->assertIsNotContent( 252 ); + $this->assertNotContains( NS_MEDIA, $subjectsNS, + "Talk namespaces should not have NS_MEDIA" ); + $this->assertNotContains( NS_SPECIAL, $subjectsNS, + "Talk namespaces should not have NS_SPECIAL" ); + } - // Bless namespace # 252 as a content namespace - $wgContentNamespaces[] = 252; + /** + * @covers NamespaceInfo::getTalkNamespaces + */ + public function testGetTalkNamespaces() { + $talkNS = $this->newObj()->getTalkNamespaces(); + $this->assertContains( NS_TALK, $talkNS, + "Subject namespaces should have NS_TALK" ); + $this->assertNotContains( NS_MAIN, $talkNS, + "Subject namespaces should not have NS_MAIN" ); - $this->assertIsContent( 252 ); + $this->assertNotContains( NS_MEDIA, $talkNS, + "Subject namespaces should not have NS_MEDIA" ); + $this->assertNotContains( NS_SPECIAL, $talkNS, + "Subject namespaces should not have NS_SPECIAL" ); + } - // Makes sure NS_MAIN was not impacted - $this->assertIsContent( NS_MAIN ); + /** + * @param int $ns + * @param bool $expected + * @param bool $capitalLinks To pass to constructor + * @param array $capitalLinkOverrides To pass to constructor + * @dataProvider provideIsCapitalized + * @covers NamespaceInfo::isCapitalized + */ + public function testIsCapitalized( + $ns, $expected, $capitalLinks = true, array $capitalLinkOverrides = [] + ) { + $obj = $this->newObj( [ + 'CapitalLinks' => $capitalLinks, + 'CapitalLinkOverrides' => $capitalLinkOverrides, + ] ); + $this->assertSame( $expected, $obj->isCapitalized( $ns ) ); } - private function assertIsWatchable( $ns ) { - $this->assertTrue( $this->obj->isWatchable( $ns ) ); + public function provideIsCapitalized() { + return [ + // Test default settings + [ NS_PROJECT, true ], + [ NS_PROJECT_TALK, true ], + [ NS_MEDIA, true ], + [ NS_FILE, true ], + + // Always capitalized no matter what + [ NS_SPECIAL, true, false ], + [ NS_USER, true, false ], + [ NS_MEDIAWIKI, true, false ], + + // Even with an override too + [ NS_SPECIAL, true, false, [ NS_SPECIAL => false ] ], + [ NS_USER, true, false, [ NS_USER => false ] ], + [ NS_MEDIAWIKI, true, false, [ NS_MEDIAWIKI => false ] ], + + // Overrides work for other namespaces + [ NS_PROJECT, false, true, [ NS_PROJECT => false ] ], + [ NS_PROJECT, true, false, [ NS_PROJECT => true ] ], + + // NS_MEDIA is treated like NS_FILE, and ignores NS_MEDIA overrides + [ NS_MEDIA, false, true, [ NS_FILE => false, NS_MEDIA => true ] ], + [ NS_MEDIA, true, false, [ NS_FILE => true, NS_MEDIA => false ] ], + [ NS_FILE, false, true, [ NS_FILE => false, NS_MEDIA => true ] ], + [ NS_FILE, true, false, [ NS_FILE => true, NS_MEDIA => false ] ], + ]; } - private function assertIsNotWatchable( $ns ) { - $this->assertFalse( $this->obj->isWatchable( $ns ) ); + /** + * @covers NamespaceInfo::hasGenderDistinction + */ + public function testHasGenderDistinction() { + $obj = $this->newObj(); + + // Namespaces with gender distinctions + $this->assertTrue( $obj->hasGenderDistinction( NS_USER ) ); + $this->assertTrue( $obj->hasGenderDistinction( NS_USER_TALK ) ); + + // Other ones, "genderless" + $this->assertFalse( $obj->hasGenderDistinction( NS_MEDIA ) ); + $this->assertFalse( $obj->hasGenderDistinction( NS_SPECIAL ) ); + $this->assertFalse( $obj->hasGenderDistinction( NS_MAIN ) ); + $this->assertFalse( $obj->hasGenderDistinction( NS_TALK ) ); } /** - * @covers NamespaceInfo::isWatchable + * @covers NamespaceInfo::isNonincludable */ - public function testIsWatchable() { - // Specials namespaces are not watchable - $this->assertIsNotWatchable( NS_MEDIA ); - $this->assertIsNotWatchable( NS_SPECIAL ); + public function testIsNonincludable() { + $obj = $this->newObj( [ 'NonincludableNamespaces' => [ NS_USER ] ] ); + $this->assertTrue( $obj->isNonincludable( NS_USER ) ); + $this->assertFalse( $obj->isNonincludable( NS_TEMPLATE ) ); + } - // Core defined namespaces are watchables - $this->assertIsWatchable( NS_MAIN ); - $this->assertIsWatchable( NS_TALK ); + /** + * @dataProvider provideGetNamespaceContentModel + * @covers NamespaceInfo::getNamespaceContentModel + * + * @param int $ns + * @param string $expected + */ + public function testGetNamespaceContentModel( $ns, $expected ) { + $obj = $this->newObj( [ 'NamespaceContentModels' => + [ NS_USER => CONTENT_MODEL_WIKITEXT, 123 => CONTENT_MODEL_JSON, 1234 => 'abcdef' ], + ] ); + $this->assertSame( $expected, $obj->getNamespaceContentModel( $ns ) ); + } - // Additional, user defined namespaces are watchables - $this->assertIsWatchable( 100 ); - $this->assertIsWatchable( 101 ); + public function provideGetNamespaceContentModel() { + return [ + [ NS_MAIN, null ], + [ NS_TALK, null ], + [ NS_USER, CONTENT_MODEL_WIKITEXT ], + [ NS_USER_TALK, null ], + [ NS_SPECIAL, null ], + [ 122, null ], + [ 123, CONTENT_MODEL_JSON ], + [ 1234, 'abcdef' ], + [ 1235, null ], + ]; } - private function assertHasSubpages( $ns ) { - $this->assertTrue( $this->obj->hasSubpages( $ns ) ); + /** + * @dataProvider provideGetCategoryLinkType + * @covers NamespaceInfo::getCategoryLinkType + * + * @param int $ns + * @param string $expected + */ + public function testGetCategoryLinkType( $ns, $expected ) { + $this->assertSame( $expected, $this->newObj()->getCategoryLinkType( $ns ) ); } - private function assertHasNotSubpages( $ns ) { - $this->assertFalse( $this->obj->hasSubpages( $ns ) ); + public function provideGetCategoryLinkType() { + return [ + [ NS_MAIN, 'page' ], + [ NS_TALK, 'page' ], + [ NS_USER, 'page' ], + [ NS_USER_TALK, 'page' ], + + [ NS_FILE, 'file' ], + [ NS_FILE_TALK, 'page' ], + + [ NS_CATEGORY, 'subcat' ], + [ NS_CATEGORY_TALK, 'page' ], + + [ 100, 'page' ], + [ 101, 'page' ], + ]; } + // %} End basic methods + + /********************************************************************************************** + * getSubject/Talk/Associated + * %{ + */ /** - * @covers NamespaceInfo::hasSubpages + * @dataProvider provideSubjectTalk + * @covers NamespaceInfo::getSubject + * @covers NamespaceInfo::getSubjectPage + * @covers NamespaceInfo::isMethodValidFor + * @covers Title::getSubjectPage + * + * @param int $subject + * @param int $talk */ - public function testHasSubpages() { - global $wgNamespacesWithSubpages; + public function testGetSubject( $subject, $talk ) { + $obj = $this->newObj(); + $this->assertSame( $subject, $obj->getSubject( $subject ) ); + $this->assertSame( $subject, $obj->getSubject( $talk ) ); + + $subjectTitleVal = new TitleValue( $subject, 'A' ); + $talkTitleVal = new TitleValue( $talk, 'A' ); + // Object will be the same one passed in if it's a subject, different but equal object if + // it's talk + $this->assertSame( $subjectTitleVal, $obj->getSubjectPage( $subjectTitleVal ) ); + $this->assertEquals( $subjectTitleVal, $obj->getSubjectPage( $talkTitleVal ) ); + + $subjectTitle = Title::makeTitle( $subject, 'A' ); + $talkTitle = Title::makeTitle( $talk, 'A' ); + $this->assertSame( $subjectTitle, $subjectTitle->getSubjectPage() ); + $this->assertEquals( $subjectTitle, $talkTitle->getSubjectPage() ); + } - // Special namespaces: - $this->assertHasNotSubpages( NS_MEDIA ); - $this->assertHasNotSubpages( NS_SPECIAL ); + /** + * @dataProvider provideSpecialNamespaces + * @covers NamespaceInfo::getSubject + * @covers NamespaceInfo::getSubjectPage + * + * @param int $ns + */ + public function testGetSubject_special( $ns ) { + $obj = $this->newObj(); + $this->assertSame( $ns, $obj->getSubject( $ns ) ); - // Namespaces without subpages - $this->assertHasNotSubpages( NS_MAIN ); + $title = new TitleValue( $ns, 'A' ); + $this->assertSame( $title, $obj->getSubjectPage( $title ) ); + } - $wgNamespacesWithSubpages[NS_MAIN] = true; - $this->assertHasSubpages( NS_MAIN ); + /** + * @dataProvider provideSubjectTalk + * @covers NamespaceInfo::getTalk + * @covers NamespaceInfo::getTalkPage + * @covers NamespaceInfo::isMethodValidFor + * @covers Title::getTalkPage + * + * @param int $subject + * @param int $talk + */ + public function testGetTalk( $subject, $talk ) { + $obj = $this->newObj(); + $this->assertSame( $talk, $obj->getTalk( $subject ) ); + $this->assertSame( $talk, $obj->getTalk( $talk ) ); + + $subjectTitleVal = new TitleValue( $subject, 'A' ); + $talkTitleVal = new TitleValue( $talk, 'A' ); + // Object will be the same one passed in if it's a talk, different but equal object if it's + // subject + $this->assertEquals( $talkTitleVal, $obj->getTalkPage( $subjectTitleVal ) ); + $this->assertSame( $talkTitleVal, $obj->getTalkPage( $talkTitleVal ) ); + + $subjectTitle = Title::makeTitle( $subject, 'A' ); + $talkTitle = Title::makeTitle( $talk, 'A' ); + $this->assertEquals( $talkTitle, $subjectTitle->getTalkPage() ); + $this->assertSame( $talkTitle, $talkTitle->getTalkPage() ); + } - $wgNamespacesWithSubpages[NS_MAIN] = false; - $this->assertHasNotSubpages( NS_MAIN ); + /** + * @dataProvider provideSpecialNamespaces + * @covers NamespaceInfo::getTalk + * @covers NamespaceInfo::isMethodValidFor + * + * @param int $ns + */ + public function testGetTalk_special( $ns ) { + $this->setExpectedException( MWException::class, + "NamespaceInfo::getTalk does not make any sense for given namespace $ns" ); + $this->newObj()->getTalk( $ns ); + } - // Some namespaces with subpages - $this->assertHasSubpages( NS_TALK ); - $this->assertHasSubpages( NS_USER ); - $this->assertHasSubpages( NS_USER_TALK ); + /** + * @dataProvider provideSpecialNamespaces + * @covers NamespaceInfo::getTalk + * @covers NamespaceInfo::getTalkPage + * @covers NamespaceInfo::isMethodValidFor + * + * @param int $ns + */ + public function testGetTalkPage_special( $ns ) { + $this->setExpectedException( MWException::class, + "NamespaceInfo::getTalk does not make any sense for given namespace $ns" ); + $this->newObj()->getTalkPage( new TitleValue( $ns, 'A' ) ); } /** - * @covers NamespaceInfo::getContentNamespaces + * @dataProvider provideSpecialNamespaces + * @covers NamespaceInfo::getTalk + * @covers NamespaceInfo::getTalkPage + * @covers NamespaceInfo::isMethodValidFor + * @covers Title::getTalkPage + * + * @param int $ns */ - public function testGetContentNamespaces() { - global $wgContentNamespaces; + public function testTitleGetTalkPage_special( $ns ) { + $this->setExpectedException( MWException::class, + "NamespaceInfo::getTalk does not make any sense for given namespace $ns" ); + Title::makeTitle( $ns, 'A' )->getTalkPage(); + } - $this->assertEquals( - [ NS_MAIN ], - $this->obj->getContentNamespaces(), - '$wgContentNamespaces is an array with only NS_MAIN by default' - ); + /** + * @dataProvider provideSpecialNamespaces + * @covers NamespaceInfo::getAssociated + * @covers NamespaceInfo::isMethodValidFor + * + * @param int $ns + */ + public function testGetAssociated_special( $ns ) { + $this->setExpectedException( MWException::class, + "NamespaceInfo::getAssociated does not make any sense for given namespace $ns" ); + $this->newObj()->getAssociated( $ns ); + } - # test !is_array( $wgcontentNamespaces ) - $wgContentNamespaces = ''; - $this->assertEquals( [ NS_MAIN ], $this->obj->getContentNamespaces() ); + /** + * @dataProvider provideSpecialNamespaces + * @covers NamespaceInfo::getAssociated + * @covers NamespaceInfo::getAssociatedPage + * @covers NamespaceInfo::isMethodValidFor + * + * @param int $ns + */ + public function testGetAssociatedPage_special( $ns ) { + $this->setExpectedException( MWException::class, + "NamespaceInfo::getAssociated does not make any sense for given namespace $ns" ); + $this->newObj()->getAssociatedPage( new TitleValue( $ns, 'A' ) ); + } - $wgContentNamespaces = false; - $this->assertEquals( [ NS_MAIN ], $this->obj->getContentNamespaces() ); + /** + * @dataProvider provideSpecialNamespaces + * @covers NamespaceInfo::getAssociated + * @covers NamespaceInfo::getAssociatedPage + * @covers NamespaceInfo::isMethodValidFor + * @covers Title::getOtherPage + * + * @param int $ns + */ + public function testTitleGetOtherPage_special( $ns ) { + $this->setExpectedException( MWException::class, + "NamespaceInfo::getAssociated does not make any sense for given namespace $ns" ); + Title::makeTitle( $ns, 'A' )->getOtherPage(); + } - $wgContentNamespaces = null; - $this->assertEquals( [ NS_MAIN ], $this->obj->getContentNamespaces() ); + /** + * @dataProvider provideSubjectTalk + * @covers NamespaceInfo::getAssociated + * @covers NamespaceInfo::getAssociatedPage + * @covers Title::getOtherPage + * + * @param int $subject + * @param int $talk + */ + public function testGetAssociated( $subject, $talk ) { + $obj = $this->newObj(); + $this->assertSame( $talk, $obj->getAssociated( $subject ) ); + $this->assertSame( $subject, $obj->getAssociated( $talk ) ); + + $subjectTitle = new TitleValue( $subject, 'A' ); + $talkTitle = new TitleValue( $talk, 'A' ); + // Object will not be the same + $this->assertEquals( $talkTitle, $obj->getAssociatedPage( $subjectTitle ) ); + $this->assertEquals( $subjectTitle, $obj->getAssociatedPage( $talkTitle ) ); + + $subjectTitle = Title::makeTitle( $subject, 'A' ); + $talkTitle = Title::makeTitle( $talk, 'A' ); + $this->assertEquals( $talkTitle, $subjectTitle->getOtherPage() ); + $this->assertEquals( $subjectTitle, $talkTitle->getOtherPage() ); + } + + public static function provideSubjectTalk() { + return [ + // Format: [ subject, talk ] + 'Main/talk' => [ NS_MAIN, NS_TALK ], + 'User/user talk' => [ NS_USER, NS_USER_TALK ], + 'Unknown namespaces also supported' => [ 106, 107 ], + ]; + } + + public static function provideSpecialNamespaces() { + return [ + 'Special' => [ NS_SPECIAL ], + 'Media' => [ NS_MEDIA ], + 'Unknown negative index' => [ -613 ], + ]; + } - $wgContentNamespaces = 5; - $this->assertEquals( [ NS_MAIN ], $this->obj->getContentNamespaces() ); + // %} End getSubject/Talk/Associated - # test $wgContentNamespaces === [] - $wgContentNamespaces = []; - $this->assertEquals( [ NS_MAIN ], $this->obj->getContentNamespaces() ); + /********************************************************************************************** + * Canonical namespaces + * %{ + */ - # test !in_array( NS_MAIN, $wgContentNamespaces ) - $wgContentNamespaces = [ NS_USER, NS_CATEGORY ]; - $this->assertEquals( - [ NS_MAIN, NS_USER, NS_CATEGORY ], - $this->obj->getContentNamespaces(), - 'NS_MAIN is forced in $wgContentNamespaces even if unwanted' - ); + // Default canonical namespaces + // %{ + private function getDefaultNamespaces() { + return [ NS_MAIN => '' ] + self::$defaultOptions['CanonicalNamespaceNames']; + } - # test other cases, return $wgcontentNamespaces as is - $wgContentNamespaces = [ NS_MAIN ]; - $this->assertEquals( - [ NS_MAIN ], - $this->obj->getContentNamespaces() + /** + * @covers NamespaceInfo::getCanonicalNamespaces + */ + public function testGetCanonicalNamespaces() { + $this->assertSame( + $this->getDefaultNamespaces(), + $this->newObj()->getCanonicalNamespaces() ); + } + + /** + * @dataProvider provideGetCanonicalName + * @covers NamespaceInfo::getCanonicalName + * + * @param int $index + * @param string|bool $expected + */ + public function testGetCanonicalName( $index, $expected ) { + $this->assertSame( $expected, $this->newObj()->getCanonicalName( $index ) ); + } + + public function provideGetCanonicalName() { + return [ + 'Main' => [ NS_MAIN, '' ], + 'Talk' => [ NS_TALK, 'Talk' ], + 'With underscore not space' => [ NS_USER_TALK, 'User_talk' ], + 'Special' => [ NS_SPECIAL, 'Special' ], + 'Nonexistent' => [ 12345, false ], + 'Nonexistent negative' => [ -12345, false ], + ]; + } + + /** + * @dataProvider provideGetCanonicalIndex + * @covers NamespaceInfo::getCanonicalIndex + * + * @param string $name + * @param int|null $expected + */ + public function testGetCanonicalIndex( $name, $expected ) { + $this->assertSame( $expected, $this->newObj()->getCanonicalIndex( $name ) ); + } + + public function provideGetCanonicalIndex() { + return [ + 'Main' => [ '', NS_MAIN ], + 'Talk' => [ 'talk', NS_TALK ], + 'Not lowercase' => [ 'Talk', null ], + 'With underscore' => [ 'user_talk', NS_USER_TALK ], + 'Space is not recognized for underscore' => [ 'user talk', null ], + '0' => [ '0', null ], + ]; + } - $wgContentNamespaces = [ NS_MAIN, NS_USER, NS_CATEGORY ]; - $this->assertEquals( - [ NS_MAIN, NS_USER, NS_CATEGORY ], - $this->obj->getContentNamespaces() + /** + * @covers NamespaceInfo::getValidNamespaces + */ + public function testGetValidNamespaces() { + $this->assertSame( + [ NS_MAIN, NS_TALK, NS_USER, NS_USER_TALK ], + $this->newObj()->getValidNamespaces() ); } + // %} End default canonical namespaces + + // No canonical namespace names + // %{ /** - * @covers NamespaceInfo::getSubjectNamespaces + * @covers NamespaceInfo::getCanonicalNamespaces */ - public function testGetSubjectNamespaces() { - $subjectsNS = $this->obj->getSubjectNamespaces(); - $this->assertContains( NS_MAIN, $subjectsNS, - "Talk namespaces should have NS_MAIN" ); - $this->assertNotContains( NS_TALK, $subjectsNS, - "Talk namespaces should have NS_TALK" ); + public function testGetCanonicalNamespaces_NoCanonicalNamespaceNames() { + $obj = $this->newObj( [ 'CanonicalNamespaceNames' => [] ] ); - $this->assertNotContains( NS_MEDIA, $subjectsNS, - "Talk namespaces should not have NS_MEDIA" ); - $this->assertNotContains( NS_SPECIAL, $subjectsNS, - "Talk namespaces should not have NS_SPECIAL" ); + $this->assertSame( [ NS_MAIN => '' ], $obj->getCanonicalNamespaces() ); } /** - * @covers NamespaceInfo::getTalkNamespaces + * @covers NamespaceInfo::getCanonicalName */ - public function testGetTalkNamespaces() { - $talkNS = $this->obj->getTalkNamespaces(); - $this->assertContains( NS_TALK, $talkNS, - "Subject namespaces should have NS_TALK" ); - $this->assertNotContains( NS_MAIN, $talkNS, - "Subject namespaces should not have NS_MAIN" ); + public function testGetCanonicalName_NoCanonicalNamespaceNames() { + $obj = $this->newObj( [ 'CanonicalNamespaceNames' => [] ] ); - $this->assertNotContains( NS_MEDIA, $talkNS, - "Subject namespaces should not have NS_MEDIA" ); - $this->assertNotContains( NS_SPECIAL, $talkNS, - "Subject namespaces should not have NS_SPECIAL" ); + $this->assertSame( '', $obj->getCanonicalName( NS_MAIN ) ); + $this->assertFalse( $obj->getCanonicalName( NS_TALK ) ); + } + + /** + * @covers NamespaceInfo::getCanonicalIndex + */ + public function testGetCanonicalIndex_NoCanonicalNamespaceNames() { + $obj = $this->newObj( [ 'CanonicalNamespaceNames' => [] ] ); + + $this->assertSame( NS_MAIN, $obj->getCanonicalIndex( '' ) ); + $this->assertNull( $obj->getCanonicalIndex( 'talk' ) ); } - private function assertIsCapitalized( $ns ) { - $this->assertTrue( $this->obj->isCapitalized( $ns ) ); + /** + * @covers NamespaceInfo::getValidNamespaces + */ + public function testGetValidNamespaces_NoCanonicalNamespaceNames() { + $obj = $this->newObj( [ 'CanonicalNamespaceNames' => [] ] ); + + $this->assertSame( [ NS_MAIN ], $obj->getValidNamespaces() ); } - private function assertIsNotCapitalized( $ns ) { - $this->assertFalse( $this->obj->isCapitalized( $ns ) ); + // %} End no canonical namespace names + + // Test extension namespaces + // %{ + private function setupExtensionNamespaces() { + $this->scopedCallback = null; + $this->scopedCallback = ExtensionRegistry::getInstance()->setAttributeForTest( + 'ExtensionNamespaces', + [ NS_MAIN => 'No effect', NS_TALK => 'No effect', 12345 => 'Extended' ] + ); } /** - * Some namespaces are always capitalized per code definition - * in NamespaceInfo::$alwaysCapitalizedNamespaces - * @covers NamespaceInfo::isCapitalized + * @covers NamespaceInfo::getCanonicalNamespaces */ - public function testIsCapitalizedHardcodedAssertions() { - // NS_MEDIA and NS_FILE are treated the same - $this->assertEquals( - $this->obj->isCapitalized( NS_MEDIA ), - $this->obj->isCapitalized( NS_FILE ), - 'NS_MEDIA and NS_FILE have same capitalization rendering' + public function testGetCanonicalNamespaces_ExtensionNamespaces() { + $this->setupExtensionNamespaces(); + + $this->assertSame( + $this->getDefaultNamespaces() + [ 12345 => 'Extended' ], + $this->newObj()->getCanonicalNamespaces() ); + } - // Boths are capitalized by default - $this->assertIsCapitalized( NS_MEDIA ); - $this->assertIsCapitalized( NS_FILE ); + /** + * @covers NamespaceInfo::getCanonicalName + */ + public function testGetCanonicalName_ExtensionNamespaces() { + $this->setupExtensionNamespaces(); + $obj = $this->newObj(); - // Always capitalized namespaces - // @see NamespaceInfo::$alwaysCapitalizedNamespaces - $this->assertIsCapitalized( NS_SPECIAL ); - $this->assertIsCapitalized( NS_USER ); - $this->assertIsCapitalized( NS_MEDIAWIKI ); + $this->assertSame( '', $obj->getCanonicalName( NS_MAIN ) ); + $this->assertSame( 'Talk', $obj->getCanonicalName( NS_TALK ) ); + $this->assertSame( 'Extended', $obj->getCanonicalName( 12345 ) ); } /** - * Follows up for testIsCapitalizedHardcodedAssertions() but alter the - * global $wgCapitalLink setting to have extended coverage. - * - * NamespaceInfo::isCapitalized() rely on two global settings: - * $wgCapitalLinkOverrides = []; by default - * $wgCapitalLinks = true; by default - * This function test $wgCapitalLinks - * - * Global setting correctness is tested against the NS_PROJECT and - * NS_PROJECT_TALK namespaces since they are not hardcoded nor specials - * @covers NamespaceInfo::isCapitalized + * @covers NamespaceInfo::getCanonicalIndex */ - public function testIsCapitalizedWithWgCapitalLinks() { - $this->assertIsCapitalized( NS_PROJECT ); - $this->assertIsCapitalized( NS_PROJECT_TALK ); + public function testGetCanonicalIndex_ExtensionNamespaces() { + $this->setupExtensionNamespaces(); + $obj = $this->newObj(); - $this->setMwGlobals( 'wgCapitalLinks', false ); + $this->assertSame( NS_MAIN, $obj->getCanonicalIndex( '' ) ); + $this->assertSame( NS_TALK, $obj->getCanonicalIndex( 'talk' ) ); + $this->assertSame( 12345, $obj->getCanonicalIndex( 'extended' ) ); + } - // hardcoded namespaces (see above function) are still capitalized: - $this->assertIsCapitalized( NS_SPECIAL ); - $this->assertIsCapitalized( NS_USER ); - $this->assertIsCapitalized( NS_MEDIAWIKI ); + /** + * @covers NamespaceInfo::getValidNamespaces + */ + public function testGetValidNamespaces_ExtensionNamespaces() { + $this->setupExtensionNamespaces(); - // setting is correctly applied - $this->assertIsNotCapitalized( NS_PROJECT ); - $this->assertIsNotCapitalized( NS_PROJECT_TALK ); + $this->assertSame( + [ NS_MAIN, NS_TALK, NS_USER, NS_USER_TALK, 12345 ], + $this->newObj()->getValidNamespaces() + ); } + // %} End extension namespaces + + // Hook namespaces + // %{ /** - * Counter part for NamespaceInfo::testIsCapitalizedWithWgCapitalLinks() now - * testing the $wgCapitalLinkOverrides global. - * - * @todo split groups of assertions in autonomous testing functions - * @covers NamespaceInfo::isCapitalized + * @return array Expected canonical namespaces */ - public function testIsCapitalizedWithWgCapitalLinkOverrides() { - global $wgCapitalLinkOverrides; + private function setupHookNamespaces() { + $callback = + function ( &$canonicalNamespaces ) { + $canonicalNamespaces[NS_MAIN] = 'Main'; + unset( $canonicalNamespaces[NS_MEDIA] ); + $canonicalNamespaces[123456] = 'Hooked'; + }; + $this->setTemporaryHook( 'CanonicalNamespaces', $callback ); + $expected = $this->getDefaultNamespaces(); + ( $callback )( $expected ); + return $expected; + } - // Test default settings - $this->assertIsCapitalized( NS_PROJECT ); - $this->assertIsCapitalized( NS_PROJECT_TALK ); + /** + * @covers NamespaceInfo::getCanonicalNamespaces + */ + public function testGetCanonicalNamespaces_HookNamespaces() { + $expected = $this->setupHookNamespaces(); - // hardcoded namespaces (see above function) are capitalized: - $this->assertIsCapitalized( NS_SPECIAL ); - $this->assertIsCapitalized( NS_USER ); - $this->assertIsCapitalized( NS_MEDIAWIKI ); + $this->assertSame( $expected, $this->newObj()->getCanonicalNamespaces() ); + } + + /** + * @covers NamespaceInfo::getCanonicalName + */ + public function testGetCanonicalName_HookNamespaces() { + $this->setupHookNamespaces(); + $obj = $this->newObj(); - // Hardcoded namespaces remains capitalized - $wgCapitalLinkOverrides[NS_SPECIAL] = false; - $wgCapitalLinkOverrides[NS_USER] = false; - $wgCapitalLinkOverrides[NS_MEDIAWIKI] = false; + $this->assertSame( 'Main', $obj->getCanonicalName( NS_MAIN ) ); + $this->assertFalse( $obj->getCanonicalName( NS_MEDIA ) ); + $this->assertSame( 'Hooked', $obj->getCanonicalName( 123456 ) ); + } - $this->assertIsCapitalized( NS_SPECIAL ); - $this->assertIsCapitalized( NS_USER ); - $this->assertIsCapitalized( NS_MEDIAWIKI ); + /** + * @covers NamespaceInfo::getCanonicalIndex + */ + public function testGetCanonicalIndex_HookNamespaces() { + $this->setupHookNamespaces(); + $obj = $this->newObj(); - $wgCapitalLinkOverrides[NS_PROJECT] = false; - $this->assertIsNotCapitalized( NS_PROJECT ); + $this->assertSame( NS_MAIN, $obj->getCanonicalIndex( 'main' ) ); + $this->assertNull( $obj->getCanonicalIndex( 'media' ) ); + $this->assertSame( 123456, $obj->getCanonicalIndex( 'hooked' ) ); + } - $wgCapitalLinkOverrides[NS_PROJECT] = true; - $this->assertIsCapitalized( NS_PROJECT ); + /** + * @covers NamespaceInfo::getValidNamespaces + */ + public function testGetValidNamespaces_HookNamespaces() { + $this->setupHookNamespaces(); - unset( $wgCapitalLinkOverrides[NS_PROJECT] ); - $this->assertIsCapitalized( NS_PROJECT ); + $this->assertSame( + [ NS_MAIN, NS_TALK, NS_USER, NS_USER_TALK, 123456 ], + $this->newObj()->getValidNamespaces() + ); } + // %} End hook namespaces + + // Extra namespaces + // %{ /** - * @covers NamespaceInfo::hasGenderDistinction + * @return NamespaceInfo */ - public function testHasGenderDistinction() { - // Namespaces with gender distinctions - $this->assertTrue( $this->obj->hasGenderDistinction( NS_USER ) ); - $this->assertTrue( $this->obj->hasGenderDistinction( NS_USER_TALK ) ); + private function setupExtraNamespaces() { + return $this->newObj( [ 'ExtraNamespaces' => + [ NS_MAIN => 'No effect', NS_TALK => 'No effect', 1234567 => 'Extra' ] + ] ); + } - // Other ones, "genderless" - $this->assertFalse( $this->obj->hasGenderDistinction( NS_MEDIA ) ); - $this->assertFalse( $this->obj->hasGenderDistinction( NS_SPECIAL ) ); - $this->assertFalse( $this->obj->hasGenderDistinction( NS_MAIN ) ); - $this->assertFalse( $this->obj->hasGenderDistinction( NS_TALK ) ); + /** + * @covers NamespaceInfo::getCanonicalNamespaces + */ + public function testGetCanonicalNamespaces_ExtraNamespaces() { + $this->assertSame( + $this->getDefaultNamespaces() + [ 1234567 => 'Extra' ], + $this->setupExtraNamespaces()->getCanonicalNamespaces() + ); } /** - * @covers NamespaceInfo::isNonincludable + * @covers NamespaceInfo::getCanonicalName */ - public function testIsNonincludable() { - global $wgNonincludableNamespaces; + public function testGetCanonicalName_ExtraNamespaces() { + $obj = $this->setupExtraNamespaces(); - $wgNonincludableNamespaces = [ NS_USER ]; + $this->assertSame( '', $obj->getCanonicalName( NS_MAIN ) ); + $this->assertSame( 'Talk', $obj->getCanonicalName( NS_TALK ) ); + $this->assertSame( 'Extra', $obj->getCanonicalName( 1234567 ) ); + } - $this->assertTrue( $this->obj->isNonincludable( NS_USER ) ); - $this->assertFalse( $this->obj->isNonincludable( NS_TEMPLATE ) ); + /** + * @covers NamespaceInfo::getCanonicalIndex + */ + public function testGetCanonicalIndex_ExtraNamespaces() { + $obj = $this->setupExtraNamespaces(); + + $this->assertNull( $obj->getCanonicalIndex( 'no effect' ) ); + $this->assertNull( $obj->getCanonicalIndex( 'no_effect' ) ); + $this->assertSame( 1234567, $obj->getCanonicalIndex( 'extra' ) ); } - private function assertSameSubject( $ns1, $ns2, $msg = '' ) { - $this->assertTrue( $this->obj->subjectEquals( $ns1, $ns2 ), $msg ); + /** + * @covers NamespaceInfo::getValidNamespaces + */ + public function testGetValidNamespaces_ExtraNamespaces() { + $this->assertSame( + [ NS_MAIN, NS_TALK, NS_USER, NS_USER_TALK, 1234567 ], + $this->setupExtraNamespaces()->getValidNamespaces() + ); } - private function assertDifferentSubject( $ns1, $ns2, $msg = '' ) { - $this->assertFalse( $this->obj->subjectEquals( $ns1, $ns2 ), $msg ); + // %} End extra namespaces + + // Canonical namespace caching + // %{ + /** + * @covers NamespaceInfo::getCanonicalNamespaces + */ + public function testGetCanonicalNamespaces_caching() { + $obj = $this->newObj(); + + // This should cache the values + $obj->getCanonicalNamespaces(); + + // Now try to alter them through nefarious means + $this->setupExtensionNamespaces(); + $this->setupHookNamespaces(); + + // Should have no effect + $this->assertSame( $this->getDefaultNamespaces(), $obj->getCanonicalNamespaces() ); } - public function provideGetCategoryLinkType() { - return [ - [ NS_MAIN, 'page' ], - [ NS_TALK, 'page' ], - [ NS_USER, 'page' ], - [ NS_USER_TALK, 'page' ], + /** + * @covers NamespaceInfo::getCanonicalName + */ + public function testGetCanonicalName_caching() { + $obj = $this->newObj(); - [ NS_FILE, 'file' ], - [ NS_FILE_TALK, 'page' ], + // This should cache the values + $obj->getCanonicalName( NS_MAIN ); - [ NS_CATEGORY, 'subcat' ], - [ NS_CATEGORY_TALK, 'page' ], + // Now try to alter them through nefarious means + $this->setupExtensionNamespaces(); + $this->setupHookNamespaces(); - [ 100, 'page' ], - [ 101, 'page' ], + // Should have no effect + $this->assertSame( '', $obj->getCanonicalName( NS_MAIN ) ); + $this->assertSame( 'Media', $obj->getCanonicalName( NS_MEDIA ) ); + $this->assertFalse( $obj->getCanonicalName( 12345 ) ); + $this->assertFalse( $obj->getCanonicalName( 123456 ) ); + } + + /** + * @covers NamespaceInfo::getCanonicalIndex + */ + public function testGetCanonicalIndex_caching() { + $obj = $this->newObj(); + + // This should cache the values + $obj->getCanonicalIndex( '' ); + + // Now try to alter them through nefarious means + $this->setupExtensionNamespaces(); + $this->setupHookNamespaces(); + + // Should have no effect + $this->assertSame( NS_MAIN, $obj->getCanonicalIndex( '' ) ); + $this->assertSame( NS_MEDIA, $obj->getCanonicalIndex( 'media' ) ); + $this->assertNull( $obj->getCanonicalIndex( 'extended' ) ); + $this->assertNull( $obj->getCanonicalIndex( 'hooked' ) ); + } + + /** + * @covers NamespaceInfo::getValidNamespaces + */ + public function testGetValidNamespaces_caching() { + $obj = $this->newObj(); + + // This should cache the values + $obj->getValidNamespaces(); + + // Now try to alter through nefarious means + $this->setupExtensionNamespaces(); + $this->setupHookNamespaces(); + + // Should have no effect + $this->assertSame( + [ NS_MAIN, NS_TALK, NS_USER, NS_USER_TALK ], + $obj->getValidNamespaces() + ); + } + + // %} End canonical namespace caching + + // Miscellaneous + // %{ + + /** + * @dataProvider provideGetValidNamespaces_misc + * @covers NamespaceInfo::getValidNamespaces + * + * @param array $namespaces List of namespace indices to return from getCanonicalNamespaces() + * (list is overwritten by a hook, so NS_MAIN doesn't have to be present) + * @param array $expected + */ + public function testGetValidNamespaces_misc( array $namespaces, array $expected ) { + // Each namespace's name is just its index + $this->setTemporaryHook( 'CanonicalNamespaces', + function ( &$canonicalNamespaces ) use ( $namespaces ) { + $canonicalNamespaces = array_combine( $namespaces, $namespaces ); + } + ); + $this->assertSame( $expected, $this->newObj()->getValidNamespaces() ); + } + + public function provideGetValidNamespaces_misc() { + return [ + 'Out of order (T109137)' => [ [ 1, 0 ], [ 0, 1 ] ], + 'Alphabetical order' => [ [ 10, 2 ], [ 2, 10 ] ], + 'Negative' => [ [ -1000, -500, -2, 0 ], [ 0 ] ], ]; } + // %} End miscellaneous + // %} End canonical namespaces + + /********************************************************************************************** + * Restriction levels + * %{ + */ + + /** + * This mock user can only have isAllowed() called on it. + * + * @param array $groups Groups for the mock user to have + * @return User + */ + private function getMockUser( array $groups = [] ) : User { + $groups[] = '*'; + + $mock = $this->createMock( User::class ); + $mock->method( 'isAllowed' )->will( $this->returnCallback( + function ( $action ) use ( $groups ) { + global $wgGroupPermissions, $wgRevokePermissions; + if ( $action == '' ) { + return true; + } + foreach ( $wgRevokePermissions as $group => $rights ) { + if ( !in_array( $group, $groups ) ) { + continue; + } + if ( isset( $rights[$action] ) && $rights[$action] ) { + return false; + } + } + foreach ( $wgGroupPermissions as $group => $rights ) { + if ( !in_array( $group, $groups ) ) { + continue; + } + if ( isset( $rights[$action] ) && $rights[$action] ) { + return true; + } + } + return false; + } + ) ); + $mock->expects( $this->never() )->method( $this->anythingBut( 'isAllowed' ) ); + return $mock; + } + /** - * @dataProvider provideGetCategoryLinkType - * @covers NamespaceInfo::getCategoryLinkType + * @dataProvider provideGetRestrictionLevels + * @covers NamespaceInfo::getRestrictionLevels * - * @param int $index - * @param string $expected + * @param array $expected + * @param int $ns + * @param User|null $user */ - public function testGetCategoryLinkType( $index, $expected ) { - $actual = $this->obj->getCategoryLinkType( $index ); - $this->assertSame( $expected, $actual, "NS $index" ); + public function testGetRestrictionLevels( array $expected, $ns, User $user = null ) { + $this->setMwGlobals( [ + 'wgGroupPermissions' => [ + '*' => [ 'edit' => true ], + 'autoconfirmed' => [ 'editsemiprotected' => true ], + 'sysop' => [ + 'editsemiprotected' => true, + 'editprotected' => true, + ], + 'privileged' => [ 'privileged' => true ], + ], + 'wgRevokePermissions' => [ + 'noeditsemiprotected' => [ 'editsemiprotected' => true ], + ], + ] ); + $obj = $this->newObj( [ + 'NamespaceProtection' => [ + NS_MAIN => 'autoconfirmed', + NS_USER => 'sysop', + 101 => [ 'editsemiprotected', 'privileged' ], + ], + ] ); + $this->assertSame( $expected, $obj->getRestrictionLevels( $ns, $user ) ); } + + public function provideGetRestrictionLevels() { + return [ + 'No namespace restriction' => [ [ '', 'autoconfirmed', 'sysop' ], NS_TALK ], + 'Restricted to autoconfirmed' => [ [ '', 'sysop' ], NS_MAIN ], + 'Restricted to sysop' => [ [ '' ], NS_USER ], + // @todo Bug -- 'sysop' protection should be allowed in this case. Someone who's + // autoconfirmed and also privileged can edit this namespace, and would be blocked by + // the sysop protection. + 'Restricted to someone in two groups' => [ [ '' ], 101 ], + + 'No special permissions' => [ [ '' ], NS_TALK, $this->getMockUser() ], + 'autoconfirmed' => [ + [ '', 'autoconfirmed' ], + NS_TALK, + $this->getMockUser( [ 'autoconfirmed' ] ) + ], + 'autoconfirmed revoked' => [ + [ '' ], + NS_TALK, + $this->getMockUser( [ 'autoconfirmed', 'noeditsemiprotected' ] ) + ], + 'sysop' => [ + [ '', 'autoconfirmed', 'sysop' ], + NS_TALK, + $this->getMockUser( [ 'sysop' ] ) + ], + 'sysop with autoconfirmed revoked (a bit silly)' => [ + [ '', 'sysop' ], + NS_TALK, + $this->getMockUser( [ 'sysop', 'noeditsemiprotected' ] ) + ], + ]; + } + + // %} End restriction levels } + +/** + * For really cool vim folding this needs to be at the end: + * vim: foldmarker=%{,%} foldmethod=marker + */