summaryrefslogtreecommitdiff
path: root/www/wiki/tests/phpunit/includes/specialpage
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/tests/phpunit/includes/specialpage')
-rw-r--r--www/wiki/tests/phpunit/includes/specialpage/AbstractChangesListSpecialPageTestCase.php151
-rw-r--r--www/wiki/tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php1098
-rw-r--r--www/wiki/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php285
-rw-r--r--www/wiki/tests/phpunit/includes/specialpage/SpecialPageTest.php104
-rw-r--r--www/wiki/tests/phpunit/includes/specialpage/SpecialPageTestHelper.php24
5 files changed, 1662 insertions, 0 deletions
diff --git a/www/wiki/tests/phpunit/includes/specialpage/AbstractChangesListSpecialPageTestCase.php b/www/wiki/tests/phpunit/includes/specialpage/AbstractChangesListSpecialPageTestCase.php
new file mode 100644
index 00000000..8b8ba0c0
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specialpage/AbstractChangesListSpecialPageTestCase.php
@@ -0,0 +1,151 @@
+<?php
+
+/**
+ * Abstract base class for shared logic when testing ChangesListSpecialPage
+ * and subclasses
+ *
+ * @group Database
+ */
+abstract class AbstractChangesListSpecialPageTestCase extends MediaWikiTestCase {
+ // Must be initialized by subclass
+ /**
+ * @var ChangesListSpecialPage
+ */
+ protected $changesListSpecialPage;
+
+ protected $oldPatrollersGroup;
+
+ protected function setUp() {
+ global $wgGroupPermissions;
+
+ parent::setUp();
+ $this->setMwGlobals( [
+ 'wgRCWatchCategoryMembership' => true,
+ 'wgUseRCPatrol' => true,
+ ] );
+
+ if ( isset( $wgGroupPermissions['patrollers'] ) ) {
+ $this->oldPatrollersGroup = $wgGroupPermissions['patrollers'];
+ }
+
+ $wgGroupPermissions['patrollers'] = [
+ 'patrol' => true,
+ ];
+
+ // Deprecated
+ $this->setTemporaryHook(
+ 'ChangesListSpecialPageFilters',
+ null
+ );
+
+ # setup the ChangesListSpecialPage (or subclass) object
+ $this->changesListSpecialPage = $this->getPage();
+ $context = $this->changesListSpecialPage->getContext();
+ $context = new DerivativeContext( $context );
+ $context->setUser( $this->getTestUser( [ 'patrollers' ] )->getUser() );
+ $this->changesListSpecialPage->setContext( $context );
+ $this->changesListSpecialPage->registerFilters();
+ }
+
+ abstract protected function getPage();
+
+ protected function tearDown() {
+ global $wgGroupPermissions;
+
+ parent::tearDown();
+
+ if ( $this->oldPatrollersGroup !== null ) {
+ $wgGroupPermissions['patrollers'] = $this->oldPatrollersGroup;
+ }
+ }
+
+ abstract public function provideParseParameters();
+
+ /**
+ * @dataProvider provideParseParameters
+ */
+ public function testParseParameters( $params, $expected ) {
+ $opts = new FormOptions();
+ foreach ( $expected as $key => $value ) {
+ // Register it as null so sets aren't rejected.
+ $opts->add(
+ $key,
+ null,
+ FormOptions::guessType( $expected )
+ );
+ }
+
+ $this->changesListSpecialPage->parseParameters(
+ $params,
+ $opts
+ );
+
+ $this->assertArrayEquals(
+ $expected,
+ $opts->getAllValues(),
+ /** ordered= */ false,
+ /** named= */ true
+ );
+ }
+
+ /**
+ * @dataProvider validateOptionsProvider
+ */
+ public function testValidateOptions( $optionsToSet, $expectedRedirect, $expectedRedirectOptions ) {
+ $redirectQuery = [];
+ $redirected = false;
+ $output = $this->getMockBuilder( OutputPage::class )
+ ->disableProxyingToOriginalMethods()
+ ->disableOriginalConstructor()
+ ->getMock();
+ $output->method( 'redirect' )->willReturnCallback(
+ function ( $url ) use ( &$redirectQuery, &$redirected ) {
+ $urlParts = wfParseUrl( $url );
+ $query = isset( $urlParts[ 'query' ] ) ? $urlParts[ 'query' ] : '';
+ parse_str( $query, $redirectQuery );
+ $redirected = true;
+ }
+ );
+ $ctx = new RequestContext();
+
+ // Give users patrol permissions so we can test that.
+ $user = $this->getTestSysop()->getUser();
+ $ctx->setUser( $user );
+
+ // Disable this hook or it could break changeType
+ // depending on which other extensions are running.
+ $this->setTemporaryHook(
+ 'ChangesListSpecialPageStructuredFilters',
+ null
+ );
+
+ $ctx->setOutput( $output );
+ $clsp = $this->changesListSpecialPage;
+ $clsp->setContext( $ctx );
+ $opts = $clsp->getDefaultOptions();
+
+ foreach ( $optionsToSet as $option => $value ) {
+ $opts->setValue( $option, $value );
+ }
+
+ $clsp->validateOptions( $opts );
+
+ $this->assertEquals( $expectedRedirect, $redirected, 'redirection' );
+
+ if ( $expectedRedirect ) {
+ if ( count( $expectedRedirectOptions ) > 0 ) {
+ $expectedRedirectOptions += [
+ 'title' => $clsp->getPageTitle()->getPrefixedText(),
+ ];
+ }
+
+ $this->assertArrayEquals(
+ $expectedRedirectOptions,
+ $redirectQuery,
+ /* $ordered= */ false,
+ /* $named= */ true,
+ 'redirection query'
+ );
+ }
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php b/www/wiki/tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php
new file mode 100644
index 00000000..aeaa1aee
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php
@@ -0,0 +1,1098 @@
+<?php
+
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * Test class for ChangesListSpecialPage class
+ *
+ * Copyright © 2011-, Antoine Musso, Stephane Bisson, Matthew Flaschen
+ *
+ * @author Antoine Musso
+ * @author Stephane Bisson
+ * @author Matthew Flaschen
+ * @group Database
+ *
+ * @covers ChangesListSpecialPage
+ */
+class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase {
+ public function setUp() {
+ parent::setUp();
+ $this->setMwGlobals( [
+ 'wgStructuredChangeFiltersShowPreference' => true,
+ ] );
+ }
+
+ protected function getPage() {
+ $mock = $this->getMockBuilder( ChangesListSpecialPage::class )
+ ->setConstructorArgs(
+ [
+ 'ChangesListSpecialPage',
+ ''
+ ]
+ )
+ ->setMethods( [ 'getPageTitle' ] )
+ ->getMockForAbstractClass();
+
+ $mock->method( 'getPageTitle' )->willReturn(
+ Title::makeTitle( NS_SPECIAL, 'ChangesListSpecialPage' )
+ );
+
+ $mock = TestingAccessWrapper::newFromObject(
+ $mock
+ );
+
+ return $mock;
+ }
+
+ private function buildQuery(
+ $requestOptions = null,
+ $user = null
+ ) {
+ $context = new RequestContext;
+ $context->setRequest( new FauxRequest( $requestOptions ) );
+ if ( $user ) {
+ $context->setUser( $user );
+ }
+
+ $this->changesListSpecialPage->setContext( $context );
+ $this->changesListSpecialPage->filterGroups = [];
+ $formOptions = $this->changesListSpecialPage->setup( null );
+
+ #  Filter out rc_timestamp conditions which depends on the test runtime
+ # This condition is not needed as of march 2, 2011 -- hashar
+ # @todo FIXME: Find a way to generate the correct rc_timestamp
+
+ $tables = [];
+ $fields = [];
+ $queryConditions = [];
+ $query_options = [];
+ $join_conds = [];
+
+ call_user_func_array(
+ [ $this->changesListSpecialPage, 'buildQuery' ],
+ [
+ &$tables,
+ &$fields,
+ &$queryConditions,
+ &$query_options,
+ &$join_conds,
+ $formOptions
+ ]
+ );
+
+ $queryConditions = array_filter(
+ $queryConditions,
+ 'ChangesListSpecialPageTest::filterOutRcTimestampCondition'
+ );
+
+ return $queryConditions;
+ }
+
+ /** helper to test SpecialRecentchanges::buildQuery() */
+ private function assertConditions(
+ $expected,
+ $requestOptions = null,
+ $message = '',
+ $user = null
+ ) {
+ $queryConditions = $this->buildQuery( $requestOptions, $user );
+
+ $this->assertEquals(
+ self::normalizeCondition( $expected ),
+ self::normalizeCondition( $queryConditions ),
+ $message
+ );
+ }
+
+ private static function normalizeCondition( $conds ) {
+ $dbr = wfGetDB( DB_REPLICA );
+ $normalized = array_map(
+ function ( $k, $v ) use ( $dbr ) {
+ if ( is_array( $v ) ) {
+ sort( $v );
+ }
+ // (Ab)use makeList() to format only this entry
+ return $dbr->makeList( [ $k => $v ], Database::LIST_AND );
+ },
+ array_keys( $conds ),
+ $conds
+ );
+ sort( $normalized );
+ return $normalized;
+ }
+
+ /** return false if condition begins with 'rc_timestamp ' */
+ private static function filterOutRcTimestampCondition( $var ) {
+ return ( is_array( $var ) || false === strpos( $var, 'rc_timestamp ' ) );
+ }
+
+ public function testRcNsFilter() {
+ $this->assertConditions(
+ [ # expected
+ "rc_namespace = '0'",
+ ],
+ [
+ 'namespace' => NS_MAIN,
+ ],
+ "rc conditions with one namespace"
+ );
+ }
+
+ public function testRcNsFilterInversion() {
+ $this->assertConditions(
+ [ # expected
+ "rc_namespace != '0'",
+ ],
+ [
+ 'namespace' => NS_MAIN,
+ 'invert' => 1,
+ ],
+ "rc conditions with namespace inverted"
+ );
+ }
+
+ public function testRcNsFilterMultiple() {
+ $this->assertConditions(
+ [ # expected
+ "rc_namespace IN ('1','2','3')",
+ ],
+ [
+ 'namespace' => '1;2;3',
+ ],
+ "rc conditions with multiple namespaces"
+ );
+ }
+
+ public function testRcNsFilterMultipleAssociated() {
+ $this->assertConditions(
+ [ # expected
+ "rc_namespace IN ('0','1','4','5','6','7')",
+ ],
+ [
+ 'namespace' => '1;4;7',
+ 'associated' => 1,
+ ],
+ "rc conditions with multiple namespaces and associated"
+ );
+ }
+
+ public function testRcNsFilterMultipleAssociatedInvert() {
+ $this->assertConditions(
+ [ # expected
+ "rc_namespace NOT IN ('2','3','8','9')",
+ ],
+ [
+ 'namespace' => '2;3;9',
+ 'associated' => 1,
+ 'invert' => 1
+ ],
+ "rc conditions with multiple namespaces, associated and inverted"
+ );
+ }
+
+ public function testRcNsFilterMultipleInvert() {
+ $this->assertConditions(
+ [ # expected
+ "rc_namespace NOT IN ('1','2','3')",
+ ],
+ [
+ 'namespace' => '1;2;3',
+ 'invert' => 1,
+ ],
+ "rc conditions with multiple namespaces inverted"
+ );
+ }
+
+ public function testRcHidemyselfFilter() {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->overrideMwServices();
+
+ $user = $this->getTestUser()->getUser();
+ $user->getActorId( wfGetDB( DB_MASTER ) );
+ $this->assertConditions(
+ [ # expected
+ "NOT((rc_actor = '{$user->getActorId()}') OR "
+ . "(rc_actor = '0' AND rc_user = '{$user->getId()}'))",
+ ],
+ [
+ 'hidemyself' => 1,
+ ],
+ "rc conditions: hidemyself=1 (logged in)",
+ $user
+ );
+
+ $user = User::newFromName( '10.11.12.13', false );
+ $id = $user->getActorId( wfGetDB( DB_MASTER ) );
+ $this->assertConditions(
+ [ # expected
+ "NOT((rc_actor = '$id') OR (rc_actor = '0' AND rc_user_text = '10.11.12.13'))",
+ ],
+ [
+ 'hidemyself' => 1,
+ ],
+ "rc conditions: hidemyself=1 (anon)",
+ $user
+ );
+ }
+
+ public function testRcHidebyothersFilter() {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->overrideMwServices();
+
+ $user = $this->getTestUser()->getUser();
+ $user->getActorId( wfGetDB( DB_MASTER ) );
+ $this->assertConditions(
+ [ # expected
+ "(rc_actor = '{$user->getActorId()}') OR "
+ . "(rc_actor = '0' AND rc_user_text = '{$user->getName()}')",
+ ],
+ [
+ 'hidebyothers' => 1,
+ ],
+ "rc conditions: hidebyothers=1 (logged in)",
+ $user
+ );
+
+ $user = User::newFromName( '10.11.12.13', false );
+ $id = $user->getActorId( wfGetDB( DB_MASTER ) );
+ $this->assertConditions(
+ [ # expected
+ "(rc_actor = '$id') OR (rc_actor = '0' AND rc_user_text = '10.11.12.13')",
+ ],
+ [
+ 'hidebyothers' => 1,
+ ],
+ "rc conditions: hidebyothers=1 (anon)",
+ $user
+ );
+ }
+
+ public function testRcHidepageedits() {
+ $this->assertConditions(
+ [ # expected
+ "rc_type != '0'",
+ ],
+ [
+ 'hidepageedits' => 1,
+ ],
+ "rc conditions: hidepageedits=1"
+ );
+ }
+
+ public function testRcHidenewpages() {
+ $this->assertConditions(
+ [ # expected
+ "rc_type != '1'",
+ ],
+ [
+ 'hidenewpages' => 1,
+ ],
+ "rc conditions: hidenewpages=1"
+ );
+ }
+
+ public function testRcHidelog() {
+ $this->assertConditions(
+ [ # expected
+ "rc_type != '3'",
+ ],
+ [
+ 'hidelog' => 1,
+ ],
+ "rc conditions: hidelog=1"
+ );
+ }
+
+ public function testRcHidehumans() {
+ $this->assertConditions(
+ [ # expected
+ 'rc_bot' => 1,
+ ],
+ [
+ 'hidebots' => 0,
+ 'hidehumans' => 1,
+ ],
+ "rc conditions: hidebots=0 hidehumans=1"
+ );
+ }
+
+ public function testRcHidepatrolledDisabledFilter() {
+ $this->setMwGlobals( 'wgUseRCPatrol', false );
+ $user = $this->getTestUser()->getUser();
+ $this->assertConditions(
+ [ # expected
+ ],
+ [
+ 'hidepatrolled' => 1,
+ ],
+ "rc conditions: hidepatrolled=1 (user not allowed)",
+ $user
+ );
+ }
+
+ public function testRcHideunpatrolledDisabledFilter() {
+ $this->setMwGlobals( 'wgUseRCPatrol', false );
+ $user = $this->getTestUser()->getUser();
+ $this->assertConditions(
+ [ # expected
+ ],
+ [
+ 'hideunpatrolled' => 1,
+ ],
+ "rc conditions: hideunpatrolled=1 (user not allowed)",
+ $user
+ );
+ }
+ public function testRcHidepatrolledFilter() {
+ $user = $this->getTestSysop()->getUser();
+ $this->assertConditions(
+ [ # expected
+ 'rc_patrolled' => 0,
+ ],
+ [
+ 'hidepatrolled' => 1,
+ ],
+ "rc conditions: hidepatrolled=1",
+ $user
+ );
+ }
+
+ public function testRcHideunpatrolledFilter() {
+ $user = $this->getTestSysop()->getUser();
+ $this->assertConditions(
+ [ # expected
+ 'rc_patrolled' => [ 1, 2 ],
+ ],
+ [
+ 'hideunpatrolled' => 1,
+ ],
+ "rc conditions: hideunpatrolled=1",
+ $user
+ );
+ }
+
+ public function testRcReviewStatusFilter() {
+ $user = $this->getTestSysop()->getUser();
+ $this->assertConditions(
+ [ #expected
+ 'rc_patrolled' => 1,
+ ],
+ [
+ 'reviewStatus' => 'manual'
+ ],
+ "rc conditions: reviewStatus=manual",
+ $user
+ );
+ $this->assertConditions(
+ [ #expected
+ 'rc_patrolled' => [ 0, 2 ],
+ ],
+ [
+ 'reviewStatus' => 'unpatrolled;auto'
+ ],
+ "rc conditions: reviewStatus=unpatrolled;auto",
+ $user
+ );
+ }
+
+ public function testRcHideminorFilter() {
+ $this->assertConditions(
+ [ # expected
+ "rc_minor = 0",
+ ],
+ [
+ 'hideminor' => 1,
+ ],
+ "rc conditions: hideminor=1"
+ );
+ }
+
+ public function testRcHidemajorFilter() {
+ $this->assertConditions(
+ [ # expected
+ "rc_minor = 1",
+ ],
+ [
+ 'hidemajor' => 1,
+ ],
+ "rc conditions: hidemajor=1"
+ );
+ }
+
+ public function testHideCategorization() {
+ $this->assertConditions(
+ [
+ # expected
+ "rc_type != '6'"
+ ],
+ [
+ 'hidecategorization' => 1
+ ],
+ "rc conditions: hidecategorization=1"
+ );
+ }
+
+ public function testFilterUserExpLevelAll() {
+ $this->assertConditions(
+ [
+ # expected
+ ],
+ [
+ 'userExpLevel' => 'registered;unregistered;newcomer;learner;experienced',
+ ],
+ "rc conditions: userExpLevel=registered;unregistered;newcomer;learner;experienced"
+ );
+ }
+
+ public function testFilterUserExpLevelRegisteredUnregistered() {
+ $this->assertConditions(
+ [
+ # expected
+ ],
+ [
+ 'userExpLevel' => 'registered;unregistered',
+ ],
+ "rc conditions: userExpLevel=registered;unregistered"
+ );
+ }
+
+ public function testFilterUserExpLevelRegisteredUnregisteredLearner() {
+ $this->assertConditions(
+ [
+ # expected
+ ],
+ [
+ 'userExpLevel' => 'registered;unregistered;learner',
+ ],
+ "rc conditions: userExpLevel=registered;unregistered;learner"
+ );
+ }
+
+ public function testFilterUserExpLevelAllExperienceLevels() {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->overrideMwServices();
+
+ $this->assertConditions(
+ [
+ # expected
+ 'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
+ ],
+ [
+ 'userExpLevel' => 'newcomer;learner;experienced',
+ ],
+ "rc conditions: userExpLevel=newcomer;learner;experienced"
+ );
+ }
+
+ public function testFilterUserExpLevelRegistrered() {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->overrideMwServices();
+
+ $this->assertConditions(
+ [
+ # expected
+ 'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
+ ],
+ [
+ 'userExpLevel' => 'registered',
+ ],
+ "rc conditions: userExpLevel=registered"
+ );
+ }
+
+ public function testFilterUserExpLevelUnregistrered() {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->overrideMwServices();
+
+ $this->assertConditions(
+ [
+ # expected
+ 'COALESCE( actor_rc_user.actor_user, rc_user ) = 0',
+ ],
+ [
+ 'userExpLevel' => 'unregistered',
+ ],
+ "rc conditions: userExpLevel=unregistered"
+ );
+ }
+
+ public function testFilterUserExpLevelRegistreredOrLearner() {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->overrideMwServices();
+
+ $this->assertConditions(
+ [
+ # expected
+ 'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
+ ],
+ [
+ 'userExpLevel' => 'registered;learner',
+ ],
+ "rc conditions: userExpLevel=registered;learner"
+ );
+ }
+
+ public function testFilterUserExpLevelUnregistreredOrExperienced() {
+ $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+ $this->overrideMwServices();
+
+ $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
+
+ $this->assertRegExp(
+ '/\(COALESCE\( actor_rc_user.actor_user, rc_user \) = 0\) OR '
+ . '\(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
+ reset( $conds ),
+ "rc conditions: userExpLevel=unregistered;experienced"
+ );
+ }
+
+ public function testFilterUserExpLevel() {
+ $now = time();
+ $this->setMwGlobals( [
+ 'wgLearnerEdits' => 10,
+ 'wgLearnerMemberSince' => 4,
+ 'wgExperiencedUserEdits' => 500,
+ 'wgExperiencedUserMemberSince' => 30,
+ ] );
+
+ $this->createUsers( [
+ 'Newcomer1' => [ 'edits' => 2, 'days' => 2 ],
+ 'Newcomer2' => [ 'edits' => 12, 'days' => 3 ],
+ 'Newcomer3' => [ 'edits' => 8, 'days' => 5 ],
+ 'Learner1' => [ 'edits' => 15, 'days' => 10 ],
+ 'Learner2' => [ 'edits' => 450, 'days' => 20 ],
+ 'Learner3' => [ 'edits' => 460, 'days' => 33 ],
+ 'Learner4' => [ 'edits' => 525, 'days' => 28 ],
+ 'Experienced1' => [ 'edits' => 538, 'days' => 33 ],
+ ], $now );
+
+ // newcomers only
+ $this->assertArrayEquals(
+ [ 'Newcomer1', 'Newcomer2', 'Newcomer3' ],
+ $this->fetchUsers( [ 'newcomer' ], $now )
+ );
+
+ // newcomers and learner
+ $this->assertArrayEquals(
+ [
+ 'Newcomer1', 'Newcomer2', 'Newcomer3',
+ 'Learner1', 'Learner2', 'Learner3', 'Learner4',
+ ],
+ $this->fetchUsers( [ 'newcomer', 'learner' ], $now )
+ );
+
+ // newcomers and more learner
+ $this->assertArrayEquals(
+ [
+ 'Newcomer1', 'Newcomer2', 'Newcomer3',
+ 'Experienced1',
+ ],
+ $this->fetchUsers( [ 'newcomer', 'experienced' ], $now )
+ );
+
+ // learner only
+ $this->assertArrayEquals(
+ [ 'Learner1', 'Learner2', 'Learner3', 'Learner4' ],
+ $this->fetchUsers( [ 'learner' ], $now )
+ );
+
+ // more experienced only
+ $this->assertArrayEquals(
+ [ 'Experienced1' ],
+ $this->fetchUsers( [ 'experienced' ], $now )
+ );
+
+ // learner and more experienced
+ $this->assertArrayEquals(
+ [
+ 'Learner1', 'Learner2', 'Learner3', 'Learner4',
+ 'Experienced1',
+ ],
+ $this->fetchUsers( [ 'learner', 'experienced' ], $now ),
+ 'Learner and more experienced'
+ );
+ }
+
+ private function createUsers( $specs, $now ) {
+ $dbw = wfGetDB( DB_MASTER );
+ foreach ( $specs as $name => $spec ) {
+ User::createNew(
+ $name,
+ [
+ 'editcount' => $spec['edits'],
+ 'registration' => $dbw->timestamp( $this->daysAgo( $spec['days'], $now ) ),
+ 'email' => 'ut',
+ ]
+ );
+ }
+ }
+
+ private function fetchUsers( $filters, $now ) {
+ $tables = [];
+ $conds = [];
+ $fields = [];
+ $query_options = [];
+ $join_conds = [];
+
+ sort( $filters );
+
+ call_user_func_array(
+ [ $this->changesListSpecialPage, 'filterOnUserExperienceLevel' ],
+ [
+ get_class( $this->changesListSpecialPage ),
+ $this->changesListSpecialPage->getContext(),
+ $this->changesListSpecialPage->getDB(),
+ &$tables,
+ &$fields,
+ &$conds,
+ &$query_options,
+ &$join_conds,
+ $filters,
+ $now
+ ]
+ );
+
+ // @todo: This is not at all safe or sane. It just blindly assumes
+ // nothing in $conds depends on any other tables.
+ $result = wfGetDB( DB_MASTER )->select(
+ 'user',
+ 'user_name',
+ array_filter( $conds ) + [ 'user_email' => 'ut' ]
+ );
+
+ $usernames = [];
+ foreach ( $result as $row ) {
+ $usernames[] = $row->user_name;
+ }
+
+ return $usernames;
+ }
+
+ private function daysAgo( $days, $now ) {
+ $secondsPerDay = 86400;
+ return $now - $days * $secondsPerDay;
+ }
+
+ public function testGetFilterGroupDefinitionFromLegacyCustomFilters() {
+ $customFilters = [
+ 'hidefoo' => [
+ 'msg' => 'showhidefoo',
+ 'default' => true,
+ ],
+
+ 'hidebar' => [
+ 'msg' => 'showhidebar',
+ 'default' => false,
+ ],
+ ];
+
+ $this->assertEquals(
+ [
+ 'name' => 'unstructured',
+ 'class' => ChangesListBooleanFilterGroup::class,
+ 'priority' => -1,
+ 'filters' => [
+ [
+ 'name' => 'hidefoo',
+ 'showHide' => 'showhidefoo',
+ 'default' => true,
+ ],
+ [
+ 'name' => 'hidebar',
+ 'showHide' => 'showhidebar',
+ 'default' => false,
+ ]
+ ],
+ ],
+ $this->changesListSpecialPage->getFilterGroupDefinitionFromLegacyCustomFilters(
+ $customFilters
+ )
+ );
+ }
+
+ public function testGetStructuredFilterJsData() {
+ $this->changesListSpecialPage->filterGroups = [];
+
+ $definition = [
+ [
+ 'name' => 'gub-group',
+ 'title' => 'gub-group-title',
+ 'class' => ChangesListBooleanFilterGroup::class,
+ 'filters' => [
+ [
+ 'name' => 'hidefoo',
+ 'label' => 'foo-label',
+ 'description' => 'foo-description',
+ 'default' => true,
+ 'showHide' => 'showhidefoo',
+ 'priority' => 2,
+ ],
+ [
+ 'name' => 'hidebar',
+ 'label' => 'bar-label',
+ 'description' => 'bar-description',
+ 'default' => false,
+ 'priority' => 4,
+ ]
+ ],
+ ],
+
+ [
+ 'name' => 'des-group',
+ 'title' => 'des-group-title',
+ 'class' => ChangesListStringOptionsFilterGroup::class,
+ 'isFullCoverage' => true,
+ 'filters' => [
+ [
+ 'name' => 'grault',
+ 'label' => 'grault-label',
+ 'description' => 'grault-description',
+ ],
+ [
+ 'name' => 'garply',
+ 'label' => 'garply-label',
+ 'description' => 'garply-description',
+ ],
+ ],
+ 'queryCallable' => function () {
+ },
+ 'default' => ChangesListStringOptionsFilterGroup::NONE,
+ ],
+
+ [
+ 'name' => 'unstructured',
+ 'class' => ChangesListBooleanFilterGroup::class,
+ 'filters' => [
+ [
+ 'name' => 'hidethud',
+ 'showHide' => 'showhidethud',
+ 'default' => true,
+ ],
+
+ [
+ 'name' => 'hidemos',
+ 'showHide' => 'showhidemos',
+ 'default' => false,
+ ],
+ ],
+ ],
+
+ ];
+
+ $this->changesListSpecialPage->registerFiltersFromDefinitions( $definition );
+
+ $this->assertArrayEquals(
+ [
+ // Filters that only display in the unstructured UI are
+ // are not included, and neither are groups that would
+ // be empty due to the above.
+ 'groups' => [
+ [
+ 'name' => 'gub-group',
+ 'title' => 'gub-group-title',
+ 'type' => ChangesListBooleanFilterGroup::TYPE,
+ 'priority' => -1,
+ 'filters' => [
+ [
+ 'name' => 'hidebar',
+ 'label' => 'bar-label',
+ 'description' => 'bar-description',
+ 'default' => false,
+ 'priority' => 4,
+ 'cssClass' => null,
+ 'conflicts' => [],
+ 'subset' => [],
+ 'defaultHighlightColor' => null
+ ],
+ [
+ 'name' => 'hidefoo',
+ 'label' => 'foo-label',
+ 'description' => 'foo-description',
+ 'default' => true,
+ 'priority' => 2,
+ 'cssClass' => null,
+ 'conflicts' => [],
+ 'subset' => [],
+ 'defaultHighlightColor' => null
+ ],
+ ],
+ 'fullCoverage' => true,
+ 'conflicts' => [],
+ ],
+
+ [
+ 'name' => 'des-group',
+ 'title' => 'des-group-title',
+ 'type' => ChangesListStringOptionsFilterGroup::TYPE,
+ 'priority' => -2,
+ 'fullCoverage' => true,
+ 'filters' => [
+ [
+ 'name' => 'grault',
+ 'label' => 'grault-label',
+ 'description' => 'grault-description',
+ 'cssClass' => null,
+ 'priority' => -2,
+ 'conflicts' => [],
+ 'subset' => [],
+ 'defaultHighlightColor' => null
+ ],
+ [
+ 'name' => 'garply',
+ 'label' => 'garply-label',
+ 'description' => 'garply-description',
+ 'cssClass' => null,
+ 'priority' => -3,
+ 'conflicts' => [],
+ 'subset' => [],
+ 'defaultHighlightColor' => null
+ ],
+ ],
+ 'conflicts' => [],
+ 'separator' => ';',
+ 'default' => ChangesListStringOptionsFilterGroup::NONE,
+ ],
+ ],
+ 'messageKeys' => [
+ 'gub-group-title',
+ 'bar-label',
+ 'bar-description',
+ 'foo-label',
+ 'foo-description',
+ 'des-group-title',
+ 'grault-label',
+ 'grault-description',
+ 'garply-label',
+ 'garply-description',
+ ],
+ ],
+ $this->changesListSpecialPage->getStructuredFilterJsData(),
+ /** ordered= */ false,
+ /** named= */ true
+ );
+ }
+
+ public function provideParseParameters() {
+ return [
+ [ 'hidebots', [ 'hidebots' => true ] ],
+
+ [ 'bots', [ 'hidebots' => false ] ],
+
+ [ 'hideminor', [ 'hideminor' => true ] ],
+
+ [ 'minor', [ 'hideminor' => false ] ],
+
+ [ 'hidemajor', [ 'hidemajor' => true ] ],
+
+ [ 'hideliu', [ 'hideliu' => true ] ],
+
+ [ 'hidepatrolled', [ 'hidepatrolled' => true ] ],
+
+ [ 'hideunpatrolled', [ 'hideunpatrolled' => true ] ],
+
+ [ 'hideanons', [ 'hideanons' => true ] ],
+
+ [ 'hidemyself', [ 'hidemyself' => true ] ],
+
+ [ 'hidebyothers', [ 'hidebyothers' => true ] ],
+
+ [ 'hidehumans', [ 'hidehumans' => true ] ],
+
+ [ 'hidepageedits', [ 'hidepageedits' => true ] ],
+
+ [ 'pagedits', [ 'hidepageedits' => false ] ],
+
+ [ 'hidenewpages', [ 'hidenewpages' => true ] ],
+
+ [ 'hidecategorization', [ 'hidecategorization' => true ] ],
+
+ [ 'hidelog', [ 'hidelog' => true ] ],
+
+ [
+ 'userExpLevel=learner;experienced',
+ [
+ 'userExpLevel' => 'learner;experienced'
+ ],
+ ],
+
+ // A few random combos
+ [
+ 'bots,hideliu,hidemyself',
+ [
+ 'hidebots' => false,
+ 'hideliu' => true,
+ 'hidemyself' => true,
+ ],
+ ],
+
+ [
+ 'minor,hideanons,categorization',
+ [
+ 'hideminor' => false,
+ 'hideanons' => true,
+ 'hidecategorization' => false,
+ ]
+ ],
+
+ [
+ 'hidehumans,bots,hidecategorization',
+ [
+ 'hidehumans' => true,
+ 'hidebots' => false,
+ 'hidecategorization' => true,
+ ],
+ ],
+
+ [
+ 'hidemyself,userExpLevel=newcomer;learner,hideminor',
+ [
+ 'hidemyself' => true,
+ 'hideminor' => true,
+ 'userExpLevel' => 'newcomer;learner',
+ ],
+ ],
+ ];
+ }
+
+ public function provideGetFilterConflicts() {
+ return [
+ [
+ "parameters" => [],
+ "expectedConflicts" => false,
+ ],
+ [
+ "parameters" => [
+ "hideliu" => true,
+ "userExpLevel" => "newcomer",
+ ],
+ "expectedConflicts" => false,
+ ],
+ [
+ "parameters" => [
+ "hideanons" => true,
+ "userExpLevel" => "learner",
+ ],
+ "expectedConflicts" => false,
+ ],
+ [
+ "parameters" => [
+ "hidemajor" => true,
+ "hidenewpages" => true,
+ "hidepageedits" => true,
+ "hidecategorization" => false,
+ "hidelog" => true,
+ "hideWikidata" => true,
+ ],
+ "expectedConflicts" => true,
+ ],
+ [
+ "parameters" => [
+ "hidemajor" => true,
+ "hidenewpages" => false,
+ "hidepageedits" => true,
+ "hidecategorization" => false,
+ "hidelog" => false,
+ "hideWikidata" => true,
+ ],
+ "expectedConflicts" => true,
+ ],
+ [
+ "parameters" => [
+ "hidemajor" => true,
+ "hidenewpages" => false,
+ "hidepageedits" => false,
+ "hidecategorization" => true,
+ "hidelog" => true,
+ "hideWikidata" => true,
+ ],
+ "expectedConflicts" => false,
+ ],
+ [
+ "parameters" => [
+ "hideminor" => true,
+ "hidenewpages" => true,
+ "hidepageedits" => true,
+ "hidecategorization" => false,
+ "hidelog" => true,
+ "hideWikidata" => true,
+ ],
+ "expectedConflicts" => false,
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideGetFilterConflicts
+ */
+ public function testGetFilterConflicts( $parameters, $expectedConflicts ) {
+ $context = new RequestContext;
+ $context->setRequest( new FauxRequest( $parameters ) );
+ $this->changesListSpecialPage->setContext( $context );
+
+ $this->assertEquals(
+ $expectedConflicts,
+ $this->changesListSpecialPage->areFiltersInConflict()
+ );
+ }
+
+ public function validateOptionsProvider() {
+ return [
+ [
+ [ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 1 ],
+ true,
+ [ 'userExpLevel' => 'unregistered', 'hidebots' => 1, ],
+ ],
+ [
+ [ 'hideanons' => 1, 'hideliu' => 1, 'hidebots' => 0 ],
+ true,
+ [ 'hidebots' => 0, 'hidehumans' => 1 ],
+ ],
+ [
+ [ 'hideanons' => 1 ],
+ true,
+ [ 'userExpLevel' => 'registered' ]
+ ],
+ [
+ [ 'hideliu' => 1 ],
+ true,
+ [ 'userExpLevel' => 'unregistered' ]
+ ],
+ [
+ [ 'hideanons' => 1, 'hidebots' => 1 ],
+ true,
+ [ 'userExpLevel' => 'registered', 'hidebots' => 1 ]
+ ],
+ [
+ [ 'hideliu' => 1, 'hidebots' => 0 ],
+ true,
+ [ 'userExpLevel' => 'unregistered', 'hidebots' => 0 ]
+ ],
+ [
+ [ 'hidemyself' => 1, 'hidebyothers' => 1 ],
+ true,
+ [],
+ ],
+ [
+ [ 'hidebots' => 1, 'hidehumans' => 1 ],
+ true,
+ [],
+ ],
+ [
+ [ 'hidepatrolled' => 1, 'hideunpatrolled' => 1 ],
+ true,
+ [],
+ ],
+ [
+ [ 'hideminor' => 1, 'hidemajor' => 1 ],
+ true,
+ [],
+ ],
+ [
+ // changeType
+ [ 'hidepageedits' => 1, 'hidenewpages' => 1, 'hidecategorization' => 1, 'hidelog' => 1, ],
+ true,
+ [],
+ ],
+ ];
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php b/www/wiki/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php
new file mode 100644
index 00000000..9ac546dc
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specialpage/SpecialPageFactoryTest.php
@@ -0,0 +1,285 @@
+<?php
+use Wikimedia\ScopedCallback;
+
+/**
+ * Factory for handling the special page list and generating SpecialPage objects.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @covers SpecialPageFactory
+ * @group SpecialPage
+ */
+class SpecialPageFactoryTest extends MediaWikiTestCase {
+
+ protected function tearDown() {
+ parent::tearDown();
+
+ SpecialPageFactory::resetList();
+ }
+
+ public function testResetList() {
+ SpecialPageFactory::resetList();
+ $this->assertContains( 'Specialpages', SpecialPageFactory::getNames() );
+ }
+
+ public function testHookNotCalledTwice() {
+ $count = 0;
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'SpecialPage_initList' => [
+ function () use ( &$count ) {
+ $count++;
+ }
+ ] ] );
+ SpecialPageFactory::resetList();
+ SpecialPageFactory::getNames();
+ SpecialPageFactory::getNames();
+ $this->assertEquals( 1, $count );
+ }
+
+ public function newSpecialAllPages() {
+ return new SpecialAllPages();
+ }
+
+ public function specialPageProvider() {
+ $specialPageTestHelper = new SpecialPageTestHelper();
+
+ return [
+ 'class name' => [ 'SpecialAllPages', false ],
+ 'closure' => [ function () {
+ return new SpecialAllPages();
+ }, false ],
+ 'function' => [ [ $this, 'newSpecialAllPages' ], false ],
+ 'callback string' => [ 'SpecialPageTestHelper::newSpecialAllPages', false ],
+ 'callback with object' => [
+ [ $specialPageTestHelper, 'newSpecialAllPages' ],
+ false
+ ],
+ 'callback array' => [
+ [ 'SpecialPageTestHelper', 'newSpecialAllPages' ],
+ false
+ ]
+ ];
+ }
+
+ /**
+ * @covers SpecialPageFactory::getPage
+ * @dataProvider specialPageProvider
+ */
+ public function testGetPage( $spec, $shouldReuseInstance ) {
+ $this->mergeMwGlobalArrayValue( 'wgSpecialPages', [ 'testdummy' => $spec ] );
+ SpecialPageFactory::resetList();
+
+ $page = SpecialPageFactory::getPage( 'testdummy' );
+ $this->assertInstanceOf( SpecialPage::class, $page );
+
+ $page2 = SpecialPageFactory::getPage( 'testdummy' );
+ $this->assertEquals( $shouldReuseInstance, $page2 === $page, "Should re-use instance:" );
+ }
+
+ /**
+ * @covers SpecialPageFactory::getNames
+ */
+ public function testGetNames() {
+ $this->mergeMwGlobalArrayValue( 'wgSpecialPages', [ 'testdummy' => SpecialAllPages::class ] );
+ SpecialPageFactory::resetList();
+
+ $names = SpecialPageFactory::getNames();
+ $this->assertInternalType( 'array', $names );
+ $this->assertContains( 'testdummy', $names );
+ }
+
+ /**
+ * @covers SpecialPageFactory::resolveAlias
+ */
+ public function testResolveAlias() {
+ $this->setMwGlobals( 'wgContLang', Language::factory( 'de' ) );
+ SpecialPageFactory::resetList();
+
+ list( $name, $param ) = SpecialPageFactory::resolveAlias( 'Spezialseiten/Foo' );
+ $this->assertEquals( 'Specialpages', $name );
+ $this->assertEquals( 'Foo', $param );
+ }
+
+ /**
+ * @covers SpecialPageFactory::getLocalNameFor
+ */
+ public function testGetLocalNameFor() {
+ $this->setMwGlobals( 'wgContLang', Language::factory( 'de' ) );
+ SpecialPageFactory::resetList();
+
+ $name = SpecialPageFactory::getLocalNameFor( 'Specialpages', 'Foo' );
+ $this->assertEquals( 'Spezialseiten/Foo', $name );
+ }
+
+ /**
+ * @covers SpecialPageFactory::getTitleForAlias
+ */
+ public function testGetTitleForAlias() {
+ $this->setMwGlobals( 'wgContLang', Language::factory( 'de' ) );
+ SpecialPageFactory::resetList();
+
+ $title = SpecialPageFactory::getTitleForAlias( 'Specialpages/Foo' );
+ $this->assertEquals( 'Spezialseiten/Foo', $title->getText() );
+ $this->assertEquals( NS_SPECIAL, $title->getNamespace() );
+ }
+
+ /**
+ * @dataProvider provideTestConflictResolution
+ */
+ public function testConflictResolution(
+ $test, $aliasesList, $alias, $expectedName, $expectedAlias, $expectWarnings
+ ) {
+ global $wgContLang;
+ $lang = clone $wgContLang;
+ $lang->mExtendedSpecialPageAliases = $aliasesList;
+ $this->setMwGlobals( 'wgContLang', $lang );
+ $this->setMwGlobals( 'wgSpecialPages',
+ array_combine( array_keys( $aliasesList ), array_keys( $aliasesList ) )
+ );
+ SpecialPageFactory::resetList();
+
+ // Catch the warnings we expect to be raised
+ $warnings = [];
+ $this->setMwGlobals( 'wgDevelopmentWarnings', true );
+ set_error_handler( function ( $errno, $errstr ) use ( &$warnings ) {
+ if ( preg_match( '/First alias \'[^\']*\' for .*/', $errstr ) ||
+ preg_match( '/Did not find a usable alias for special page .*/', $errstr )
+ ) {
+ $warnings[] = $errstr;
+ return true;
+ }
+ return false;
+ } );
+ $reset = new ScopedCallback( 'restore_error_handler' );
+
+ list( $name, /*...*/ ) = SpecialPageFactory::resolveAlias( $alias );
+ $this->assertEquals( $expectedName, $name, "$test: Alias to name" );
+ $result = SpecialPageFactory::getLocalNameFor( $name );
+ $this->assertEquals( $expectedAlias, $result, "$test: Alias to name to alias" );
+
+ $gotWarnings = count( $warnings );
+ if ( $gotWarnings !== $expectWarnings ) {
+ $this->fail( "Expected $expectWarnings warning(s), but got $gotWarnings:\n" .
+ implode( "\n", $warnings )
+ );
+ }
+ }
+
+ /**
+ * @dataProvider provideTestConflictResolution
+ */
+ public function testConflictResolutionReversed(
+ $test, $aliasesList, $alias, $expectedName, $expectedAlias, $expectWarnings
+ ) {
+ // Make sure order doesn't matter by reversing the list
+ $aliasesList = array_reverse( $aliasesList );
+ return $this->testConflictResolution(
+ $test, $aliasesList, $alias, $expectedName, $expectedAlias, $expectWarnings
+ );
+ }
+
+ public function provideTestConflictResolution() {
+ return [
+ [
+ 'Canonical name wins',
+ [ 'Foo' => [ 'Foo', 'Bar' ], 'Baz' => [ 'Foo', 'BazPage', 'Baz2' ] ],
+ 'Foo',
+ 'Foo',
+ 'Foo',
+ 1,
+ ],
+
+ [
+ 'Doesn\'t redirect to a different special page\'s canonical name',
+ [ 'Foo' => [ 'Foo', 'Bar' ], 'Baz' => [ 'Foo', 'BazPage', 'Baz2' ] ],
+ 'Baz',
+ 'Baz',
+ 'BazPage',
+ 1,
+ ],
+
+ [
+ 'Canonical name wins even if not aliased',
+ [ 'Foo' => [ 'FooPage' ], 'Baz' => [ 'Foo', 'BazPage', 'Baz2' ] ],
+ 'Foo',
+ 'Foo',
+ 'FooPage',
+ 1,
+ ],
+
+ [
+ 'Doesn\'t redirect to a different special page\'s canonical name even if not aliased',
+ [ 'Foo' => [ 'FooPage' ], 'Baz' => [ 'Foo', 'BazPage', 'Baz2' ] ],
+ 'Baz',
+ 'Baz',
+ 'BazPage',
+ 1,
+ ],
+
+ [
+ 'First local name beats non-first',
+ [ 'First' => [ 'Foo' ], 'NonFirst' => [ 'Bar', 'Foo' ] ],
+ 'Foo',
+ 'First',
+ 'Foo',
+ 0,
+ ],
+
+ [
+ 'Doesn\'t redirect to a different special page\'s first alias',
+ [
+ 'Foo' => [ 'Foo' ],
+ 'First' => [ 'Bar' ],
+ 'Baz' => [ 'Foo', 'Bar', 'BazPage', 'Baz2' ]
+ ],
+ 'Baz',
+ 'Baz',
+ 'BazPage',
+ 1,
+ ],
+
+ [
+ 'Doesn\'t redirect wrong even if all aliases conflict',
+ [
+ 'Foo' => [ 'Foo' ],
+ 'First' => [ 'Bar' ],
+ 'Baz' => [ 'Foo', 'Bar' ]
+ ],
+ 'Baz',
+ 'Baz',
+ 'Baz',
+ 2,
+ ],
+
+ ];
+ }
+
+ public function testGetAliasListRecursion() {
+ $called = false;
+ $this->mergeMwGlobalArrayValue( 'wgHooks', [
+ 'SpecialPage_initList' => [
+ function () use ( &$called ) {
+ SpecialPageFactory::getLocalNameFor( 'Specialpages' );
+ $called = true;
+ }
+ ],
+ ] );
+ SpecialPageFactory::resetList();
+ SpecialPageFactory::getLocalNameFor( 'Specialpages' );
+ $this->assertTrue( $called, 'Recursive call succeeded' );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/specialpage/SpecialPageTest.php b/www/wiki/tests/phpunit/includes/specialpage/SpecialPageTest.php
new file mode 100644
index 00000000..2ad39729
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specialpage/SpecialPageTest.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @covers SpecialPage
+ *
+ * @group Database
+ *
+ * @author Katie Filbert < aude.wiki@gmail.com >
+ */
+class SpecialPageTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( [
+ 'wgScript' => '/index.php',
+ 'wgContLang' => Language::factory( 'en' )
+ ] );
+ }
+
+ /**
+ * @dataProvider getTitleForProvider
+ */
+ public function testGetTitleFor( $expectedName, $name ) {
+ $title = SpecialPage::getTitleFor( $name );
+ $expected = Title::makeTitle( NS_SPECIAL, $expectedName );
+ $this->assertEquals( $expected, $title );
+ }
+
+ public function getTitleForProvider() {
+ return [
+ [ 'UserLogin', 'Userlogin' ]
+ ];
+ }
+
+ /**
+ * @expectedException PHPUnit_Framework_Error_Notice
+ */
+ public function testInvalidGetTitleFor() {
+ $title = SpecialPage::getTitleFor( 'cat' );
+ $expected = Title::makeTitle( NS_SPECIAL, 'Cat' );
+ $this->assertEquals( $expected, $title );
+ }
+
+ /**
+ * @expectedException PHPUnit_Framework_Error_Notice
+ * @dataProvider getTitleForWithWarningProvider
+ */
+ public function testGetTitleForWithWarning( $expected, $name ) {
+ $title = SpecialPage::getTitleFor( $name );
+ $this->assertEquals( $expected, $title );
+ }
+
+ public function getTitleForWithWarningProvider() {
+ return [
+ [ Title::makeTitle( NS_SPECIAL, 'UserLogin' ), 'UserLogin' ]
+ ];
+ }
+
+ /**
+ * @dataProvider requireLoginAnonProvider
+ */
+ public function testRequireLoginAnon( $expected, $reason, $title ) {
+ $specialPage = new SpecialPage( 'Watchlist', 'viewmywatchlist' );
+
+ $user = User::newFromId( 0 );
+ $specialPage->getContext()->setUser( $user );
+ $specialPage->getContext()->setLanguage( Language::factory( 'en' ) );
+
+ $this->setExpectedException( UserNotLoggedIn::class, $expected );
+
+ // $specialPage->requireLogin( [ $reason [, $title ] ] )
+ call_user_func_array(
+ [ $specialPage, 'requireLogin' ],
+ array_filter( [ $reason, $title ] )
+ );
+ }
+
+ public function requireLoginAnonProvider() {
+ $lang = 'en';
+
+ $expected1 = wfMessage( 'exception-nologin-text' )->inLanguage( $lang )->text();
+ $expected2 = wfMessage( 'about' )->inLanguage( $lang )->text();
+
+ return [
+ [ $expected1, null, null ],
+ [ $expected2, 'about', null ],
+ [ $expected2, 'about', 'about' ],
+ ];
+ }
+
+ public function testRequireLoginNotAnon() {
+ $specialPage = new SpecialPage( 'Watchlist', 'viewmywatchlist' );
+
+ $user = User::newFromName( "UTSysop" );
+ $specialPage->getContext()->setUser( $user );
+
+ $specialPage->requireLogin();
+
+ // no exception thrown, logged in use can access special page
+ $this->assertTrue( true );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/specialpage/SpecialPageTestHelper.php b/www/wiki/tests/phpunit/includes/specialpage/SpecialPageTestHelper.php
new file mode 100644
index 00000000..37e29dcb
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specialpage/SpecialPageTestHelper.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+class SpecialPageTestHelper {
+
+ public static function newSpecialAllPages() {
+ return new SpecialAllPages();
+ }
+
+}