From f909c62d649cba43c9a9d7928db0129ccb3d0961 Mon Sep 17 00:00:00 2001 From: Antoine Musso Date: Mon, 21 Feb 2011 22:17:06 +0000 Subject: [PATCH] improve namespace related methods MWNamespace::getTalk() could give erroneus results when using it on specials namespaces (NS_MEDIA, NS_SPECIAL). It now use MWNamespace::isMethodValidFor() which will throw an exception if a special namespace was given. MWNamespace::getSubject() is now returning identity for specials namespaces. New MWNamespace::getAssociated() used to find out the subject page of a talk page and vice versa. Special namespaces will results in an exception. TESTS: Added tests for almost complete code coverage. Functions relying on global $wgCanonicalNamespaces are still incomplete though. MWNamespace::isMovable() needs more assertions. Tests results (ignoring incomplete tests output): $ php phpunit.php --filter MWNamespace PHPUnit 3.5.10 by Sebastian Bergmann. .........IIIII.......... Time: 1 second, Memory: 31.75Mb OK, but incomplete or skipped tests! Tests: 24, Assertions: 99, Incomplete: 5. --- includes/Namespace.php | 43 ++ tests/phpunit/includes/MWNamespaceTest.php | 441 +++++++++++++++++++++ 2 files changed, 484 insertions(+) create mode 100644 tests/phpunit/includes/MWNamespaceTest.php diff --git a/includes/Namespace.php b/includes/Namespace.php index 2e4b789f4b..48c527f99b 100644 --- a/includes/Namespace.php +++ b/includes/Namespace.php @@ -53,6 +53,22 @@ class MWNamespace { */ private static $alwaysCapitalizedNamespaces = array( NS_SPECIAL, NS_USER, NS_MEDIAWIKI ); + /** + * Trow an exception when trying to get the subject or talk page + * for a given namespace where it does not make sens. + * Special namespaces are defined in includes/define.php and have + * a value below 0 (ex: NS_SPECIAL = -1 , NS_MEDIA = -2) + * + * @param $ns Int: namespace index + */ + private static function isMethodValidFor( $index, $method ) { + if( $index < NS_MAIN ) { + throw new MWException( "$method does not make any sens for given namespace $index" ); + return false; + } + return true; + } + /** * Can pages in the given namespace be moved? * @@ -92,6 +108,7 @@ class MWNamespace { * @return int */ public static function getTalk( $index ) { + self::isMethodValidFor( $index, __METHOD__ ); return self::isTalk( $index ) ? $index : $index + 1; @@ -99,16 +116,42 @@ class MWNamespace { /** * Get the subject namespace index for a given namespace + * Special namespaces (NS_MEDIA, NS_SPECIAL) are always the subject. * * @param $index Int: Namespace index * @return int */ public static function getSubject( $index ) { + # Handle special namespaces + if( $index < NS_MAIN ) { + return $index; + } + return self::isTalk( $index ) ? $index - 1 : $index; } + /** + * Get the associated namespace. + * For talk namespaces, returns the subject (non-talk) namespace + * For subject (non-talk) namespaces, returns the talk namespace + * + * @param $index Int: namespace index + * @return int or null if no associated namespace could be found + */ + public static function getAssociated( $index ) { + self::isMethodValidFor( $index, __METHOD__ ); + + if( self::isMain( $index ) ) { + return self::getTalk( $index ); + } elseif( self::isTalk( $index ) ) { + return self::getSubject( $index ); + } else { + return null; + } + } + /** * Returns whether the specified namespace exists */ diff --git a/tests/phpunit/includes/MWNamespaceTest.php b/tests/phpunit/includes/MWNamespaceTest.php new file mode 100644 index 0000000000..9caf93f16c --- /dev/null +++ b/tests/phpunit/includes/MWNamespaceTest.php @@ -0,0 +1,441 @@ +object = new MWNamespace; + } + + /** + * Tears down the fixture, for example, closes a network connection. + * This method is called after a test is executed. + */ + protected function tearDown() { + } + + +#### START OF TESTS ######################################################### + + /** + * @todo Write more texts, handle $wgAllowImageMoving setting + */ + public function testIsMovable() { + $this->assertFalse( MWNamespace::isMovable( NS_CATEGORY ) ); + # FIXME : write more tests!! + } + + /** + * Please make sure to change testIsTalk() if you change the assertions below + */ + public function testIsMain() { + // Special namespaces + $this->assertTrue( MWNamespace::isMain( NS_MEDIA ) ); + $this->assertTrue( MWNamespace::isMain( NS_SPECIAL ) ); + + // Subject pages + $this->assertTrue( MWNamespace::isMain( NS_MAIN ) ); + $this->assertTrue( MWNamespace::isMain( NS_USER ) ); + $this->assertTrue( MWNamespace::isMain( 100 ) ); # user defined + + // Talk pages + $this->assertFalse( MWNamespace::isMain( NS_TALK ) ); + $this->assertFalse( MWNamespace::isMain( NS_USER_TALK ) ); + $this->assertFalse( MWNamespace::isMain( 101 ) ); # user defined + } + + /** + * Reverse of testIsMain(). + * Please update testIsMain() if you change assertions below + */ + public function testIsTalk() { + // Special namespaces + $this->assertFalse( MWNamespace::isTalk( NS_MEDIA ) ); + $this->assertFalse( MWNamespace::isTalk( NS_SPECIAL ) ); + + // Subject pages + $this->assertFalse( MWNamespace::isTalk( NS_MAIN ) ); + $this->assertFalse( MWNamespace::isTalk( NS_USER ) ); + $this->assertFalse( MWNamespace::isTalk( 100 ) ); # user defined + + // Talk pages + $this->assertTrue( MWNamespace::isTalk( NS_TALK ) ); + $this->assertTrue( MWNamespace::isTalk( NS_USER_TALK ) ); + $this->assertTrue( MWNamespace::isTalk( 101 ) ); # user defined + } + + /** + * Regular getTalk() calls + * Namespaces without a talk page (NS_MEDIA, NS_SPECIAL) are tested in + * the function testGetTalkExceptions() + */ + public function testGetTalk() { + $this->assertEquals( MWNamespace::getTalk( NS_MAIN), NS_TALK ); + } + + /** + * Exceptions with getTalk() + * NS_MEDIA and NS_SPECIAL do not have talk pages. MediaWiki raise an exception for them. + * @expectedException MWException + */ + public function testGetTalkExceptions() { + $this->assertNull( MWNamespace::getAssociated( NS_MEDIA ) ); + $this->assertNull( MWNamespace::getAssociated( NS_SPECIAL ) ); + } + + /** + * Regular getAssociated() calls + * Namespaces without an associated page (NS_MEDIA, NS_SPECIAL) are tested in + * the function testGetAssociatedExceptions() + */ + public function testGetAssociated() { + $this->assertEquals( MWNamespace::getAssociated( NS_MAIN ), NS_TALK ); + $this->assertEquals( MWNamespace::getAssociated( NS_TALK ), NS_MAIN ); + + } + + ### Exceptions with getAssociated() + ### NS_MEDIA and NS_SPECIAL do not have talk pages. MediaWiki raises + ### an exception for them. + /** + * @expectedException MWException + */ + public function testGetAssociatedExceptionsForNsMedia() { + $this->assertNull( MWNamespace::getAssociated( NS_MEDIA ) ); + } + /** + * @expectedException MWException + */ + public function testGetAssociatedExceptionsForNsSpecial() { + $this->assertNull( MWNamespace::getAssociated( NS_SPECIAL ) ); + } + + /** + */ + public function testGetSubject() { + // Special namespaces are their own subjects + $this->assertEquals( MWNamespace::getSubject( NS_MEDIA ), NS_MEDIA ); + $this->assertEquals( MWNamespace::getSubject( NS_SPECIAL ), NS_SPECIAL ); + + $this->assertEquals( MWNamespace::getSubject( NS_TALK ), NS_MAIN ); + $this->assertEquals( MWNamespace::getSubject( NS_USER_TALK ), NS_USER ); + } + + /** + * @todo Implement testExists(). + */ + public function testExists() { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.' + ); + } + + /** + * @todo Implement testGetCanonicalNamespaces(). + */ + public function testGetCanonicalNamespaces() { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.' + ); + } + + /** + * @todo Implement testGetCanonicalName(). + */ + public function testGetCanonicalName() { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.' + ); + } + + /** + * @todo Implement testGetCanonicalIndex(). + */ + public function testGetCanonicalIndex() { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.' + ); + } + + /** + * @todo Implement testGetValidNamespaces(). + */ + public function testGetValidNamespaces() { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.' + ); + } + + /** + */ + public function testCanTalk() { + $this->assertFalse( MWNamespace::canTalk( NS_MEDIA ) ); + $this->assertFalse( MWNamespace::canTalk( NS_SPECIAL ) ); + + $this->assertTrue( MWNamespace::canTalk( NS_MAIN ) ); + $this->assertTrue( MWNamespace::canTalk( NS_TALK ) ); + $this->assertTrue( MWNamespace::canTalk( NS_USER ) ); + $this->assertTrue( MWNamespace::canTalk( NS_USER_TALK ) ); + + // User defined namespaces + $this->assertTrue( MWNamespace::canTalk( 100 ) ); + $this->assertTrue( MWNamespace::canTalk( 101 ) ); + } + + /** + */ + public function testIsContent() { + // NS_MAIN is a content namespace per DefaultSettings.php + // and per function definition. + $this->assertTrue( MWNamespace::isContent( NS_MAIN ) ); + + // Other namespaces which are not expected to be content + $this->assertFalse( MWNamespace::isContent( NS_MEDIA ) ); + $this->assertFalse( MWNamespace::isContent( NS_SPECIAL ) ); + $this->assertFalse( MWNamespace::isContent( NS_TALK ) ); + $this->assertFalse( MWNamespace::isContent( NS_USER ) ); + $this->assertFalse( MWNamespace::isContent( NS_CATEGORY ) ); + // User defined namespace: + $this->assertFalse( MWNamespace::isContent( 100 ) ); + } + + /** + * Similar to testIsContent() but alters the $wgContentNamespaces + * global variable. + */ + public function testIsContentWithAdditionsInWgContentNamespaces() { + // NS_MAIN is a content namespace per DefaultSettings.php + // and per function definition. + $this->assertTrue( MWNamespace::isContent( NS_MAIN ) ); + + // Tests that user defined namespace #252 is not content: + $this->assertFalse( MWNamespace::isContent( 252 ) ); + + # FIXME: is global saving really required for PHPUnit? + // Bless namespace # 252 as a content namespace + global $wgContentNamespaces; + $savedGlobal = $wgContentNamespaces; + $wgContentNamespaces[] = 252; + $this->assertTrue( MWNamespace::isContent( 252 ) ); + + // Makes sure NS_MAIN was not impacted + $this->assertTrue( MWNamespace::isContent( NS_MAIN ) ); + + // Restore global + $wgContentNamespaces = $savedGlobal; + + // Verify namespaces after global restauration + $this->assertTrue( MWNamespace::isContent( NS_MAIN ) ); + $this->assertFalse( MWNamespace::isContent( 252 ) ); + } + + public function testIsWatchable() { + // Specials namespaces are not watchable + $this->assertFalse( MWNamespace::isWatchable( NS_MEDIA ) ); + $this->assertFalse( MWNamespace::isWatchable( NS_SPECIAL ) ); + + // Core defined namespaces are watchables + $this->assertTrue( MWNamespace::isWatchable( NS_MAIN ) ); + $this->assertTrue( MWNamespace::isWatchable( NS_TALK ) ); + + // Additional, user defined namespaces are watchables + $this->assertTrue( MWNamespace::isWatchable( 100 ) ); + $this->assertTrue( MWNamespace::isWatchable( 101 ) ); + } + + public function testHasSubpages() { + // Special namespaces: + $this->assertFalse( MWNamespace::hasSubpages( NS_MEDIA ) ); + $this->assertFalse( MWNamespace::hasSubpages( NS_SPECIAL ) ); + + // namespaces without subpages + $this->assertFalse( MWNamespace::hasSubpages( NS_MAIN ) ); + + // Some namespaces with subpages + $this->assertTrue( MWNamespace::hasSubpages( NS_TALK ) ); + $this->assertTrue( MWNamespace::hasSubpages( NS_USER ) ); + $this->assertTrue( MWNamespace::hasSubpages( NS_USER_TALK ) ); + } + + /** + */ + public function testGetContentNamespaces() { + $this->assertEquals( + MWNamespace::getcontentNamespaces(), + array( NS_MAIN), + '$wgContentNamespaces is an array with only NS_MAIN by default' + ); + + global $wgContentNamespaces; + + # test !is_array( $wgcontentNamespaces ) + $wgContentNamespaces = ''; + $this->assertEquals( MWNamespace::getcontentNamespaces(), NS_MAIN ); + $wgContentNamespaces = false; + $this->assertEquals( MWNamespace::getcontentNamespaces(), NS_MAIN ); + $wgContentNamespaces = null; + $this->assertEquals( MWNamespace::getcontentNamespaces(), NS_MAIN ); + $wgContentNamespaces = 5; + $this->assertEquals( MWNamespace::getcontentNamespaces(), NS_MAIN ); + + # test $wgContentNamespaces === array() + $wgContentNamespaces = array(); + $this->assertEquals( MWNamespace::getcontentNamespaces(), NS_MAIN ); + + # test !in_array( NS_MAIN, $wgContentNamespaces ) + $wgContentNamespaces = array( NS_USER, NS_CATEGORY ); + $this->assertEquals( + MWNamespace::getcontentNamespaces(), + array( NS_MAIN, NS_USER, NS_CATEGORY ), + 'NS_MAIN is forced in wgContentNamespaces even if unwanted' + ); + + # test other cases, return $wgcontentNamespaces as is + $wgContentNamespaces = array( NS_MAIN ); + $this->assertEquals( + MWNamespace::getcontentNamespaces(), + array( NS_MAIN ) + ); + + $wgContentNamespaces = array( NS_MAIN, NS_USER, NS_CATEGORY ); + $this->assertEquals( + MWNamespace::getcontentNamespaces(), + array( NS_MAIN, NS_USER, NS_CATEGORY ) + ); + + } + + /** + * Some namespaces are always capitalized per code definition + * in MWNamespace::$alwaysCapitalizedNamespaces + */ + public function testIsCapitalizedHardcodedAssertions() { + // NS_MEDIA and NS_FILE are treated the same + $this->assertEquals( + MWNamespace::isCapitalized( NS_MEDIA ), + MWNamespace::isCapitalized( NS_FILE ), + 'NS_MEDIA and NS_FILE have same capitalization rendering' + ); + + // Boths are capitalized by default + $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIA ) ); + $this->assertTrue( MWNamespace::isCapitalized( NS_FILE ) ); + + // Always capitalized namespaces + // @see MWNamespace::$alwaysCapitalizedNamespaces + $this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL ) ); + $this->assertTrue( MWNamespace::isCapitalized( NS_USER ) ); + $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) ); + } + + /** + * Follows up for testIsCapitalizedHardcodedAssertions() but alter the + * global $wgCapitalLink setting to have extended coverage. + * + * MWNamespace::isCapitalized() rely on two global settings: + * $wgCapitalLinkOverrides = array(); 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 + */ + public function testIsCapitalizedWithWgCapitalLinks() { + global $wgCapitalLinks; + // Save the global to easily reset to MediaWiki default settings + $savedGlobal = $wgCapitalLinks; + + $wgCapitalLinks = true; + $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) ); + $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT_TALK ) ); + + $wgCapitalLinks = false; + // hardcoded namespaces (see above function) are still capitalized: + $this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL ) ); + $this->assertTrue( MWNamespace::isCapitalized( NS_USER ) ); + $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) ); + // setting is correctly applied + $this->assertFalse( MWNamespace::isCapitalized( NS_PROJECT ) ); + $this->assertFalse( MWNamespace::isCapitalized( NS_PROJECT_TALK ) ); + + // reset global state: + $wgCapitalLinks = $savedGlobal; + } + + /** + * Counter part for MWNamespace::testIsCapitalizedWithWgCapitalLinks() now + * testing the $wgCapitalLinkOverrides global. + * + * @todo split groups of assertions in autonomous testing functions + */ + public function testIsCapitalizedWithWgCapitalLinkOverrides() { + global $wgCapitalLinkOverrides; + // Save the global to easily reset to MediaWiki default settings + $savedGlobal = $wgCapitalLinkOverrides; + + // Test default settings + $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) ); + $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT_TALK ) ); + // hardcoded namespaces (see above function) are capitalized: + $this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL ) ); + $this->assertTrue( MWNamespace::isCapitalized( NS_USER ) ); + $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) ); + + // Hardcoded namespaces remains capitalized + $wgCapitalLinkOverrides[NS_SPECIAL] = false; + $wgCapitalLinkOverrides[NS_USER] = false; + $wgCapitalLinkOverrides[NS_MEDIAWIKI] = false; + $this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL ) ); + $this->assertTrue( MWNamespace::isCapitalized( NS_USER ) ); + $this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) ); + + $wgCapitalLinkOverrides = $savedGlobal; + $wgCapitalLinkOverrides[NS_PROJECT] = false; + $this->assertFalse( MWNamespace::isCapitalized( NS_PROJECT ) ); + $wgCapitalLinkOverrides[NS_PROJECT] = true ; + $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) ); + unset( $wgCapitalLinkOverrides[NS_PROJECT] ); + $this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) ); + + // reset global state: + $wgCapitalLinkOverrides = $savedGlobal; + } + + public function testHasGenderDistinction() { + // Namespaces with gender distinctions + $this->assertTrue( MWNamespace::hasGenderDistinction( NS_USER ) ); + $this->assertTrue( MWNamespace::hasGenderDistinction( NS_USER_TALK ) ); + + // Other ones, "genderless" + $this->assertFalse( MWNamespace::hasGenderDistinction( NS_MEDIA ) ); + $this->assertFalse( MWNamespace::hasGenderDistinction( NS_SPECIAL ) ); + $this->assertFalse( MWNamespace::hasGenderDistinction( NS_MAIN ) ); + $this->assertFalse( MWNamespace::hasGenderDistinction( NS_TALK ) ); + + } +} +?> -- 2.20.1