wiki.techinc.nl/tests/phpunit/includes/title/NamespaceInfoTest.php
Tim Starling 68c433bd23 Hooks::run() call site migration
Migrate all callers of Hooks::run() to use the new
HookContainer/HookRunner system.

General principles:
* Use DI if it is already used. We're not changing the way state is
  managed in this patch.
* HookContainer is always injected, not HookRunner. HookContainer
  is a service, it's a more generic interface, it is the only
  thing that provides isRegistered() which is needed in some cases,
  and a HookRunner can be efficiently constructed from it
  (confirmed by benchmark). Because HookContainer is needed
  for object construction, it is also needed by all factories.
* "Ask your friendly local base class". Big hierarchies like
  SpecialPage and ApiBase have getHookContainer() and getHookRunner()
  methods in the base class, and classes that extend that base class
  are not expected to know or care where the base class gets its
  HookContainer from.
* ProtectedHookAccessorTrait provides protected getHookContainer() and
  getHookRunner() methods, getting them from the global service
  container. The point of this is to ease migration to DI by ensuring
  that call sites ask their local friendly base class rather than
  getting a HookRunner from the service container directly.
* Private $this->hookRunner. In some smaller classes where accessor
  methods did not seem warranted, there is a private HookRunner property
  which is accessed directly. Very rarely (two cases), there is a
  protected property, for consistency with code that conventionally
  assumes protected=private, but in cases where the class might actually
  be overridden, a protected accessor is preferred over a protected
  property.
* The last resort: Hooks::runner(). Mostly for static, file-scope and
  global code. In a few cases it was used for objects with broken
  construction schemes, out of horror or laziness.

Constructors with new required arguments:
* AuthManager
* BadFileLookup
* BlockManager
* ClassicInterwikiLookup
* ContentHandlerFactory
* ContentSecurityPolicy
* DefaultOptionsManager
* DerivedPageDataUpdater
* FullSearchResultWidget
* HtmlCacheUpdater
* LanguageFactory
* LanguageNameUtils
* LinkRenderer
* LinkRendererFactory
* LocalisationCache
* MagicWordFactory
* MessageCache
* NamespaceInfo
* PageEditStash
* PageHandlerFactory
* PageUpdater
* ParserFactory
* PermissionManager
* RevisionStore
* RevisionStoreFactory
* SearchEngineConfig
* SearchEngineFactory
* SearchFormWidget
* SearchNearMatcher
* SessionBackend
* SpecialPageFactory
* UserNameUtils
* UserOptionsManager
* WatchedItemQueryService
* WatchedItemStore

Constructors with new optional arguments:
* DefaultPreferencesFactory
* Language
* LinkHolderArray
* MovePage
* Parser
* ParserCache
* PasswordReset
* Router

setHookContainer() now required after construction:
* AuthenticationProvider
* ResourceLoaderModule
* SearchEngine

Change-Id: Id442b0dbe43aba84bd5cf801d86dedc768b082c7
2020-05-30 14:23:28 +00:00

1337 lines
37 KiB
PHP

<?php
/**
* @author Antoine Musso
* @copyright Copyright © 2011, Antoine Musso
* @file
*/
use MediaWiki\Config\ServiceOptions;
use MediaWiki\HookContainer\HookContainer;
use MediaWiki\Linker\LinkTarget;
use MediaWiki\MediaWikiServices;
class NamespaceInfoTest extends MediaWikiTestCase {
use TestAllServiceOptionsUsed;
/**********************************************************************************************
* Shared code
* %{
*/
private $scopedCallback;
public function setUp() : void {
parent::setUp();
// Boo, there's still some global state in the class :(
global $wgHooks;
$hooks = $wgHooks;
unset( $hooks['CanonicalNamespaces'] );
$this->setMwGlobals( 'wgHooks', $hooks );
$this->scopedCallback =
ExtensionRegistry::getInstance()->setAttributeForTest( 'ExtensionNamespaces', [] );
}
public function tearDown() : void {
$this->scopedCallback = null;
parent::tearDown();
}
private const DEFAULT_OPTIONS = [
'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' => [],
'NamespacesWithSubpages' => [
NS_TALK => true,
NS_USER => true,
NS_USER_TALK => true,
],
'NonincludableNamespaces' => [],
];
/**
* @return HookContainer
*/
private function getHookContainer() {
return MediaWikiServices::getInstance()->getHookContainer();
}
private function newObj( array $options = [] ) : NamespaceInfo {
return new NamespaceInfo(
new LoggedServiceOptions(
self::$serviceOptionsAccessLog,
NamespaceInfo::CONSTRUCTOR_OPTIONS,
$options, self::DEFAULT_OPTIONS
),
$this->getHookContainer()
);
}
// %} End shared code
/**********************************************************************************************
* Basic methods
* %{
*/
/**
* @covers NamespaceInfo::__construct
* @dataProvider provideConstructor
* @param ServiceOptions $options
* @param string|null $expectedExceptionText
*/
public function testConstructor( ServiceOptions $options, $expectedExceptionText = null ) {
if ( $expectedExceptionText !== null ) {
$this->expectException( \Wikimedia\Assert\PreconditionException::class );
$this->expectExceptionMessage( $expectedExceptionText );
}
new NamespaceInfo( $options, $this->getHookContainer() );
$this->assertTrue( true );
}
public function provideConstructor() {
return [
[ new ServiceOptions( NamespaceInfo::CONSTRUCTOR_OPTIONS, self::DEFAULT_OPTIONS ) ],
[ new ServiceOptions( [], [] ), 'Required options missing: ' ],
[ new ServiceOptions(
array_merge( NamespaceInfo::CONSTRUCTOR_OPTIONS, [ 'invalid' ] ),
self::DEFAULT_OPTIONS,
[ 'invalid' => '' ]
), 'Unsupported options passed: invalid' ],
];
}
/**
* @dataProvider provideIsMovable
* @covers NamespaceInfo::isMovable
*
* @param bool $expected
* @param int $ns
* @param bool $allowImageMoving
*/
public function testIsMovable( $expected, $ns, $allowImageMoving = true ) {
$obj = $this->newObj( [ 'AllowImageMoving' => $allowImageMoving ] );
$this->assertSame( $expected, $obj->isMovable( $ns ) );
}
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 ],
];
}
/**
* @param int $ns
* @param bool $expected
* @dataProvider provideIsSubject
* @covers NamespaceInfo::isSubject
*/
public function testIsSubject( $ns, $expected ) {
$this->assertSame( $expected, $this->newObj()->isSubject( $ns ) );
}
/**
* @param int $ns
* @param bool $expected
* @dataProvider provideIsSubject
* @covers NamespaceInfo::isTalk
*/
public function testIsTalk( $ns, $expected ) {
$this->assertSame( !$expected, $this->newObj()->isTalk( $ns ) );
}
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 ],
];
}
/**
* @covers NamespaceInfo::exists
* @dataProvider provideExists
* @param int $ns
* @param bool $expected
*/
public function testExists( $ns, $expected ) {
$this->assertSame( $expected, $this->newObj()->exists( $ns ) );
}
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 equivalence on things like 'MAIN' == 0
* and 'MAIN' == NS_MAIN.
* @covers NamespaceInfo::equals
*/
public function testEquals() {
$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( $ns1, $ns2, $expected ) {
$this->assertSame( $expected, $this->newObj()->subjectEquals( $ns1, $ns2 ) );
}
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 ],
];
}
/**
* @dataProvider provideHasTalkNamespace
* @covers NamespaceInfo::hasTalkNamespace
*
* @param int $ns
* @param bool $expected
*/
public function testHasTalkNamespace( $ns, $expected ) {
$this->assertSame( $expected, $this->newObj()->hasTalkNamespace( $ns ) );
}
public function provideHasTalkNamespace() {
return [
[ NS_MEDIA, false ],
[ NS_SPECIAL, false ],
[ NS_MAIN, true ],
[ NS_TALK, true ],
[ NS_USER, true ],
[ NS_USER_TALK, true ],
[ 100, true ],
[ 101, true ],
];
}
/**
* @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 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 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 testIsWatchable( $ns, $expected ) {
$this->assertSame( $expected, $this->newObj()->isWatchable( $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 ) );
}
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 ],
];
}
/**
* @param mixed $contentNamespaces To pass to constructor
* @param array $expected
* @dataProvider provideGetContentNamespaces
* @covers NamespaceInfo::getContentNamespaces
*/
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 ] ],
// Empty array
[ [], [ NS_MAIN ] ],
// NS_MAIN is forced to be content even if unwanted
[ [ NS_USER, NS_CATEGORY ], [ NS_MAIN, NS_USER, NS_CATEGORY ] ],
// In other cases, return as-is
[ [ NS_MAIN ], [ NS_MAIN ] ],
[ [ NS_MAIN, NS_USER, NS_CATEGORY ], [ NS_MAIN, NS_USER, NS_CATEGORY ] ],
];
}
/**
* @covers NamespaceInfo::getSubjectNamespaces
*/
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" );
$this->assertNotContains( NS_MEDIA, $subjectsNS,
"Talk namespaces should not have NS_MEDIA" );
$this->assertNotContains( NS_SPECIAL, $subjectsNS,
"Talk namespaces should not have NS_SPECIAL" );
}
/**
* @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->assertNotContains( NS_MEDIA, $talkNS,
"Subject namespaces should not have NS_MEDIA" );
$this->assertNotContains( NS_SPECIAL, $talkNS,
"Subject namespaces should not have NS_SPECIAL" );
}
/**
* @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 ) );
}
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 ] ],
];
}
/**
* @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::isNonincludable
*/
public function testIsNonincludable() {
$obj = $this->newObj( [ 'NonincludableNamespaces' => [ NS_USER ] ] );
$this->assertTrue( $obj->isNonincludable( NS_USER ) );
$this->assertFalse( $obj->isNonincludable( NS_TEMPLATE ) );
}
/**
* @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 ) );
}
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 ],
];
}
/**
* @dataProvider provideGetCategoryLinkType
* @covers NamespaceInfo::getCategoryLinkType
*
* @param int $ns
* @param string $expected
*/
public function testGetCategoryLinkType( $ns, $expected ) {
$this->assertSame( $expected, $this->newObj()->getCategoryLinkType( $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
* %{
*/
/**
* @dataProvider provideSubjectTalk
* @covers NamespaceInfo::getSubject
* @covers NamespaceInfo::getSubjectPage
* @covers NamespaceInfo::isMethodValidFor
* @covers Title::getSubjectPage
*
* @param int $subject
* @param int $talk
*/
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() );
}
/**
* @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 ) );
$title = new TitleValue( $ns, 'A' );
$this->assertSame( $title, $obj->getSubjectPage( $title ) );
}
/**
* @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() );
}
/**
* @dataProvider provideSpecialNamespaces
* @covers NamespaceInfo::getTalk
* @covers NamespaceInfo::isMethodValidFor
*
* @param int $ns
*/
public function testGetTalk_special( $ns ) {
$this->expectException( MWException::class );
$this->expectExceptionMessage(
"NamespaceInfo::getTalk does not make any sense for given namespace $ns"
);
$this->newObj()->getTalk( $ns );
}
/**
* @dataProvider provideSpecialNamespaces
* @covers NamespaceInfo::getAssociated
* @covers NamespaceInfo::isMethodValidFor
*
* @param int $ns
*/
public function testGetAssociated_special( $ns ) {
$this->expectException( MWException::class );
$this->expectExceptionMessage(
"NamespaceInfo::getAssociated does not make any sense for given namespace $ns"
);
$this->newObj()->getAssociated( $ns );
}
public static function provideCanHaveTalkPage() {
return [
[ new TitleValue( NS_MAIN, 'Test' ), true ],
[ new TitleValue( NS_TALK, 'Test' ), true ],
[ new TitleValue( NS_USER, 'Test' ), true ],
[ new TitleValue( NS_SPECIAL, 'Test' ), false ],
[ new TitleValue( NS_MEDIA, 'Test' ), false ],
[ new TitleValue( NS_MAIN, '', 'Kittens' ), false ],
[ new TitleValue( NS_MAIN, 'Kittens', '', 'acme' ), false ],
];
}
/**
* @dataProvider provideCanHaveTalkPage
* @covers NamespaceInfo::canHaveTalkPage
*/
public function testCanHaveTalkPage( LinkTarget $t, $expected ) {
$actual = $this->newObj()->canHaveTalkPage( $t );
$this->assertEquals( $expected, $actual, $t->getDBkey() );
}
public static function provideGetTalkPage_good() {
return [
[ new TitleValue( NS_MAIN, 'Test' ), new TitleValue( NS_TALK, 'Test' ) ],
[ new TitleValue( NS_TALK, 'Test' ), new TitleValue( NS_TALK, 'Test' ) ],
[ new TitleValue( NS_USER, 'Test' ), new TitleValue( NS_USER_TALK, 'Test' ) ],
];
}
/**
* @dataProvider provideGetTalkPage_good
* @covers NamespaceInfo::getTalk
* @covers NamespaceInfo::getTalkPage
* @covers NamespaceInfo::isMethodValidFor
*/
public function testGetTalkPage_good( LinkTarget $t, LinkTarget $expected ) {
$actual = $this->newObj()->getTalkPage( $t );
$this->assertEquals( $expected, $actual, $t->getDBkey() );
}
public static function provideGetTalkPage_bad() {
return [
[ new TitleValue( NS_SPECIAL, 'Test' ) ],
[ new TitleValue( NS_MEDIA, 'Test' ) ],
[ new TitleValue( NS_MAIN, '', 'Kittens' ) ],
[ new TitleValue( NS_MAIN, 'Kittens', '', 'acme' ) ],
];
}
/**
* @dataProvider provideGetTalkPage_bad
* @covers NamespaceInfo::getTalk
* @covers NamespaceInfo::getTalkPage
* @covers NamespaceInfo::isMethodValidFor
*/
public function testGetTalkPage_bad( LinkTarget $t ) {
$this->expectException( MWException::class );
$this->newObj()->getTalkPage( $t );
}
/**
* @dataProvider provideGetTalkPage_bad
* @covers NamespaceInfo::getAssociated
* @covers NamespaceInfo::getAssociatedPage
* @covers NamespaceInfo::isMethodValidFor
*/
public function testGetAssociatedPage_bad( LinkTarget $t ) {
$this->expectException( MWException::class );
$this->newObj()->getAssociatedPage( $t );
}
/**
* @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 ],
];
}
// %} End getSubject/Talk/Associated
/**********************************************************************************************
* Canonical namespaces
* %{
*/
// Default canonical namespaces
// %{
private function getDefaultNamespaces() {
return [ NS_MAIN => '' ] + self::DEFAULT_OPTIONS['CanonicalNamespaceNames'];
}
/**
* @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 ],
];
}
/**
* @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::getCanonicalNamespaces
*/
public function testGetCanonicalNamespaces_NoCanonicalNamespaceNames() {
$obj = $this->newObj( [ 'CanonicalNamespaceNames' => [] ] );
$this->assertSame( [ NS_MAIN => '' ], $obj->getCanonicalNamespaces() );
}
/**
* @covers NamespaceInfo::getCanonicalName
*/
public function testGetCanonicalName_NoCanonicalNamespaceNames() {
$obj = $this->newObj( [ 'CanonicalNamespaceNames' => [] ] );
$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' ) );
}
/**
* @covers NamespaceInfo::getValidNamespaces
*/
public function testGetValidNamespaces_NoCanonicalNamespaceNames() {
$obj = $this->newObj( [ 'CanonicalNamespaceNames' => [] ] );
$this->assertSame( [ NS_MAIN ], $obj->getValidNamespaces() );
}
// %} 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' ]
);
}
/**
* @covers NamespaceInfo::getCanonicalNamespaces
*/
public function testGetCanonicalNamespaces_ExtensionNamespaces() {
$this->setupExtensionNamespaces();
$this->assertSame(
$this->getDefaultNamespaces() + [ 12345 => 'Extended' ],
$this->newObj()->getCanonicalNamespaces()
);
}
/**
* @covers NamespaceInfo::getCanonicalName
*/
public function testGetCanonicalName_ExtensionNamespaces() {
$this->setupExtensionNamespaces();
$obj = $this->newObj();
$this->assertSame( '', $obj->getCanonicalName( NS_MAIN ) );
$this->assertSame( 'Talk', $obj->getCanonicalName( NS_TALK ) );
$this->assertSame( 'Extended', $obj->getCanonicalName( 12345 ) );
}
/**
* @covers NamespaceInfo::getCanonicalIndex
*/
public function testGetCanonicalIndex_ExtensionNamespaces() {
$this->setupExtensionNamespaces();
$obj = $this->newObj();
$this->assertSame( NS_MAIN, $obj->getCanonicalIndex( '' ) );
$this->assertSame( NS_TALK, $obj->getCanonicalIndex( 'talk' ) );
$this->assertSame( 12345, $obj->getCanonicalIndex( 'extended' ) );
}
/**
* @covers NamespaceInfo::getValidNamespaces
*/
public function testGetValidNamespaces_ExtensionNamespaces() {
$this->setupExtensionNamespaces();
$this->assertSame(
[ NS_MAIN, NS_TALK, NS_USER, NS_USER_TALK, 12345 ],
$this->newObj()->getValidNamespaces()
);
}
// %} End extension namespaces
// Hook namespaces
// %{
/**
* @return array Expected canonical namespaces
*/
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;
}
/**
* @covers NamespaceInfo::getCanonicalNamespaces
*/
public function testGetCanonicalNamespaces_HookNamespaces() {
$expected = $this->setupHookNamespaces();
$this->assertSame( $expected, $this->newObj()->getCanonicalNamespaces() );
}
/**
* @covers NamespaceInfo::getCanonicalName
*/
public function testGetCanonicalName_HookNamespaces() {
$this->setupHookNamespaces();
$obj = $this->newObj();
$this->assertSame( 'Main', $obj->getCanonicalName( NS_MAIN ) );
$this->assertFalse( $obj->getCanonicalName( NS_MEDIA ) );
$this->assertSame( 'Hooked', $obj->getCanonicalName( 123456 ) );
}
/**
* @covers NamespaceInfo::getCanonicalIndex
*/
public function testGetCanonicalIndex_HookNamespaces() {
$this->setupHookNamespaces();
$obj = $this->newObj();
$this->assertSame( NS_MAIN, $obj->getCanonicalIndex( 'main' ) );
$this->assertNull( $obj->getCanonicalIndex( 'media' ) );
$this->assertSame( 123456, $obj->getCanonicalIndex( 'hooked' ) );
}
/**
* @covers NamespaceInfo::getValidNamespaces
*/
public function testGetValidNamespaces_HookNamespaces() {
$this->setupHookNamespaces();
$this->assertSame(
[ NS_MAIN, NS_TALK, NS_USER, NS_USER_TALK, 123456 ],
$this->newObj()->getValidNamespaces()
);
}
// %} End hook namespaces
// Extra namespaces
// %{
/**
* @return NamespaceInfo
*/
private function setupExtraNamespaces() {
return $this->newObj( [ 'ExtraNamespaces' =>
[ NS_MAIN => 'No effect', NS_TALK => 'No effect', 1234567 => 'Extra' ]
] );
}
/**
* @covers NamespaceInfo::getCanonicalNamespaces
*/
public function testGetCanonicalNamespaces_ExtraNamespaces() {
$this->assertSame(
$this->getDefaultNamespaces() + [ 1234567 => 'Extra' ],
$this->setupExtraNamespaces()->getCanonicalNamespaces()
);
}
/**
* @covers NamespaceInfo::getCanonicalName
*/
public function testGetCanonicalName_ExtraNamespaces() {
$obj = $this->setupExtraNamespaces();
$this->assertSame( '', $obj->getCanonicalName( NS_MAIN ) );
$this->assertSame( 'Talk', $obj->getCanonicalName( NS_TALK ) );
$this->assertSame( 'Extra', $obj->getCanonicalName( 1234567 ) );
}
/**
* @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' ) );
}
/**
* @covers NamespaceInfo::getValidNamespaces
*/
public function testGetValidNamespaces_ExtraNamespaces() {
$this->assertSame(
[ NS_MAIN, NS_TALK, NS_USER, NS_USER_TALK, 1234567 ],
$this->setupExtraNamespaces()->getValidNamespaces()
);
}
// %} 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() );
}
/**
* @covers NamespaceInfo::getCanonicalName
*/
public function testGetCanonicalName_caching() {
$obj = $this->newObj();
// This should cache the values
$obj->getCanonicalName( NS_MAIN );
// Now try to alter them through nefarious means
$this->setupExtensionNamespaces();
$this->setupHookNamespaces();
// 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
* %{
*/
/**
* TODO: This is superceeded by PermissionManagerTest::testGetNamespaceRestrictionLevels
* Remove when deprecated method is removed.
* @dataProvider provideGetRestrictionLevels
* @covers NamespaceInfo::getRestrictionLevels
*
* @param array $expected
* @param int $ns
* @param array|null $groups
* @throws MWException
*/
public function testGetRestrictionLevels( array $expected, $ns, array $groups = null ) {
$this->setMwGlobals( [
'wgGroupPermissions' => [
'*' => [ 'edit' => true ],
'autoconfirmed' => [ 'editsemiprotected' => true ],
'sysop' => [
'editsemiprotected' => true,
'editprotected' => true,
],
'privileged' => [ 'privileged' => true ],
],
'wgRevokePermissions' => [
'noeditsemiprotected' => [ 'editsemiprotected' => true ],
],
'wgNamespaceProtection' => [
NS_MAIN => 'autoconfirmed',
NS_USER => 'sysop',
101 => [ 'editsemiprotected', 'privileged' ],
],
'wgRestrictionLevels' => [ '', 'autoconfirmed', 'sysop' ],
'wgAutopromote' => []
] );
$obj = $this->newObj();
$user = $groups === null ? null : $this->getTestUser( $groups )->getUser();
$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 ],
'Restricted to someone in two groups' => [ [ '', 'sysop' ], 101 ],
'No special permissions' => [ [ '' ], NS_TALK, [] ],
'autoconfirmed' => [
[ '', 'autoconfirmed' ],
NS_TALK,
[ 'autoconfirmed' ]
],
'autoconfirmed revoked' => [
[ '' ],
NS_TALK,
[ 'autoconfirmed', 'noeditsemiprotected' ]
],
'sysop' => [
[ '', 'autoconfirmed', 'sysop' ],
NS_TALK,
[ 'sysop' ]
],
'sysop with autoconfirmed revoked (a bit silly)' => [
[ '', 'sysop' ],
NS_TALK,
[ 'sysop', 'noeditsemiprotected' ]
],
];
}
// %} End restriction levels
/**
* @coversNothing
*/
public function testAllServiceOptionsUsed() {
$this->assertAllServiceOptionsUsed();
}
}
/**
* For really cool vim folding this needs to be at the end:
* vim: foldmarker=%{,%} foldmethod=marker
*/