summaryrefslogtreecommitdiff
path: root/www/wiki/tests/phpunit/includes/specials
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/tests/phpunit/includes/specials')
-rw-r--r--www/wiki/tests/phpunit/includes/specials/ContribsPagerTest.php118
-rw-r--r--www/wiki/tests/phpunit/includes/specials/ImageListPagerTest.php21
-rw-r--r--www/wiki/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php80
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialBlankPageTest.php25
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialBooksourcesTest.php51
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialEditWatchlistTest.php50
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialMIMESearchTest.php49
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialMyLanguageTest.php78
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialPageDataTest.php146
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialPageExecutor.php129
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialPageTestBase.php72
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialPreferencesTest.php58
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialRecentchangesTest.php52
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialSearchTest.php296
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialShortpagesTest.php43
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialUncategorizedcategoriesTest.php63
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialUploadTest.php29
-rw-r--r--www/wiki/tests/phpunit/includes/specials/SpecialWatchlistTest.php192
18 files changed, 1552 insertions, 0 deletions
diff --git a/www/wiki/tests/phpunit/includes/specials/ContribsPagerTest.php b/www/wiki/tests/phpunit/includes/specials/ContribsPagerTest.php
new file mode 100644
index 00000000..1147805c
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/ContribsPagerTest.php
@@ -0,0 +1,118 @@
+<?php
+
+/**
+ * @group Database
+ */
+class ContribsPagerTest extends MediaWikiTestCase {
+ /** @var ContribsPager */
+ private $pager;
+
+ function setUp() {
+ $context = new RequestContext();
+ $this->pager = new ContribsPager( $context, [
+ 'start' => '2017-01-01',
+ 'end' => '2017-02-02',
+ ] );
+
+ parent::setUp();
+ }
+
+ /**
+ * @covers ContribsPager::processDateFilter
+ * @dataProvider dateFilterOptionProcessingProvider
+ * @param array $inputOpts Input options
+ * @param array $expectedOpts Expected options
+ */
+ public function testDateFilterOptionProcessing( $inputOpts, $expectedOpts ) {
+ $this->assertArraySubset( $expectedOpts, ContribsPager::processDateFilter( $inputOpts ) );
+ }
+
+ public static function dateFilterOptionProcessingProvider() {
+ return [
+ [ [ 'start' => '2016-05-01',
+ 'end' => '2016-06-01',
+ 'year' => null,
+ 'month' => null ],
+ [ 'start' => '2016-05-01',
+ 'end' => '2016-06-01' ] ],
+ [ [ 'start' => '2016-05-01',
+ 'end' => '2016-06-01',
+ 'year' => '',
+ 'month' => '' ],
+ [ 'start' => '2016-05-01',
+ 'end' => '2016-06-01' ] ],
+ [ [ 'start' => '2016-05-01',
+ 'end' => '2016-06-01',
+ 'year' => '2012',
+ 'month' => '5' ],
+ [ 'start' => '',
+ 'end' => '2012-05-31' ] ],
+ [ [ 'start' => '',
+ 'end' => '',
+ 'year' => '2012',
+ 'month' => '5' ],
+ [ 'start' => '',
+ 'end' => '2012-05-31' ] ],
+ [ [ 'start' => '',
+ 'end' => '',
+ 'year' => '2012',
+ 'month' => '' ],
+ [ 'start' => '',
+ 'end' => '2012-12-31' ] ],
+ ];
+ }
+
+ /**
+ * @covers ContribsPager::isQueryableRange
+ * @dataProvider provideQueryableRanges
+ */
+ public function testQueryableRanges( $ipRange ) {
+ $this->setMwGlobals( [
+ 'wgRangeContributionsCIDRLimit' => [
+ 'IPv4' => 16,
+ 'IPv6' => 32,
+ ],
+ ] );
+
+ $this->assertTrue(
+ $this->pager->isQueryableRange( $ipRange ),
+ "$ipRange is a queryable IP range"
+ );
+ }
+
+ public function provideQueryableRanges() {
+ return [
+ [ '116.17.184.5/32' ],
+ [ '0.17.184.5/16' ],
+ [ '2000::/32' ],
+ [ '2001:db8::/128' ],
+ ];
+ }
+
+ /**
+ * @covers ContribsPager::isQueryableRange
+ * @dataProvider provideUnqueryableRanges
+ */
+ public function testUnqueryableRanges( $ipRange ) {
+ $this->setMwGlobals( [
+ 'wgRangeContributionsCIDRLimit' => [
+ 'IPv4' => 16,
+ 'IPv6' => 32,
+ ],
+ ] );
+
+ $this->assertFalse(
+ $this->pager->isQueryableRange( $ipRange ),
+ "$ipRange is not a queryable IP range"
+ );
+ }
+
+ public function provideUnqueryableRanges() {
+ return [
+ [ '116.17.184.5/33' ],
+ [ '0.17.184.5/15' ],
+ [ '2000::/31' ],
+ [ '2001:db8::/9999' ],
+ ];
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/ImageListPagerTest.php b/www/wiki/tests/phpunit/includes/specials/ImageListPagerTest.php
new file mode 100644
index 00000000..10c6d04c
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/ImageListPagerTest.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * Test class for ImageListPagerTest class.
+ *
+ * Copyright © 2013, Antoine Musso
+ * Copyright © 2013, Siebrand Mazeland
+ * Copyright © 2013, Wikimedia Foundation Inc.
+ *
+ * @group Database
+ */
+class ImageListPagerTest extends MediaWikiTestCase {
+ /**
+ * @expectedException MWException
+ * @expectedExceptionMessage invalid_field
+ * @covers ImageListPager::formatValue
+ */
+ public function testFormatValuesThrowException() {
+ $page = new ImageListPager( RequestContext::getMain() );
+ $page->formatValue( 'invalid_field', 'invalid_value' );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php b/www/wiki/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php
new file mode 100644
index 00000000..d53a9b8f
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Test class to run the query of most of all our special pages
+ *
+ * Copyright © 2011, Antoine Musso
+ *
+ * @author Antoine Musso
+ */
+
+/**
+ * @group Database
+ * @covers QueryPage<extended>
+ */
+class QueryAllSpecialPagesTest extends MediaWikiTestCase {
+
+ /**
+ * @var SpecialPage[]
+ */
+ private $queryPages;
+
+ /** List query pages that can not be tested automatically */
+ protected $manualTest = [
+ LinkSearchPage::class
+ ];
+
+ /**
+ * Pages whose query use the same DB table more than once.
+ * This is used to skip testing those pages when run against a MySQL backend
+ * which does not support reopening a temporary table. See upstream bug:
+ * https://bugs.mysql.com/bug.php?id=10327
+ */
+ protected $reopensTempTable = [
+ BrokenRedirects::class,
+ ];
+
+ /**
+ * Initialize all query page objects
+ */
+ function __construct() {
+ parent::__construct();
+
+ foreach ( QueryPage::getPages() as $page ) {
+ $class = $page[0];
+ $name = $page[1];
+ if ( !in_array( $class, $this->manualTest ) ) {
+ $this->queryPages[$class] = SpecialPageFactory::getPage( $name );
+ }
+ }
+ }
+
+ /**
+ * Test SQL for each of our QueryPages objects
+ * @group Database
+ */
+ public function testQuerypageSqlQuery() {
+ global $wgDBtype;
+
+ foreach ( $this->queryPages as $page ) {
+ // With MySQL, skips special pages reopening a temporary table
+ // See https://bugs.mysql.com/bug.php?id=10327
+ if (
+ $wgDBtype === 'mysql'
+ && in_array( $page->getName(), $this->reopensTempTable )
+ ) {
+ $this->markTestSkipped( "SQL query for page {$page->getName()} "
+ . "can not be tested on MySQL backend (it reopens a temporary table)" );
+ continue;
+ }
+
+ $msg = "SQL query for page {$page->getName()} should give a result wrapper object";
+
+ $result = $page->reallyDoQuery( 50 );
+ if ( $result instanceof ResultWrapper ) {
+ $this->assertTrue( true, $msg );
+ } else {
+ $this->assertFalse( false, $msg );
+ }
+ }
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialBlankPageTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialBlankPageTest.php
new file mode 100644
index 00000000..e0d059fb
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialBlankPageTest.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @license GNU GPL v2+
+ * @author Addshore
+ *
+ * @covers SpecialBlankpage
+ */
+class SpecialBlankPageTest extends SpecialPageTestBase {
+
+ /**
+ * Returns a new instance of the special page under test.
+ *
+ * @return SpecialPage
+ */
+ protected function newSpecialPage() {
+ return new SpecialBlankpage();
+ }
+
+ public function testHasWikiMsg() {
+ list( $html, ) = $this->executeSpecialPage();
+ $this->assertContains( wfMessage( 'intentionallyblankpage' )->text(), $html );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialBooksourcesTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialBooksourcesTest.php
new file mode 100644
index 00000000..9c71261e
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialBooksourcesTest.php
@@ -0,0 +1,51 @@
+<?php
+class SpecialBooksourcesTest extends SpecialPageTestBase {
+ public static function provideISBNs() {
+ return [
+ [ '978-0-300-14424-6', true ],
+ [ '0-14-020652-3', true ],
+ [ '020652-3', false ],
+ [ '9781234567897', true ],
+ [ '1-4133-0454-0', true ],
+ [ '978-1413304541', true ],
+ [ '0136091814', true ],
+ [ '0136091812', false ],
+ [ '9780136091813', true ],
+ [ '9780136091817', false ],
+ [ '123456789X', true ],
+
+ // T69021
+ [ '1413304541', false ],
+ [ '141330454X', false ],
+ [ '1413304540', true ],
+ [ '14133X4540', false ],
+ [ '97814133X4541', false ],
+ [ '978035642615X', false ],
+ [ '9781413304541', true ],
+ [ '9780356426150', true ],
+ ];
+ }
+
+ /**
+ * @covers SpecialBookSources::isValidISBN
+ * @dataProvider provideISBNs
+ */
+ public function testIsValidISBN( $isbn, $isValid ) {
+ $this->assertSame( $isValid, SpecialBookSources::isValidISBN( $isbn ) );
+ }
+
+ protected function newSpecialPage() {
+ return new SpecialBookSources();
+ }
+
+ /**
+ * @covers SpecialBookSources::execute
+ */
+ public function testExecute() {
+ list( $html, ) = $this->executeSpecialPage( 'Invalid', null, 'qqx' );
+ $this->assertContains( '(booksources-invalid-isbn)', $html );
+ list( $html, ) = $this->executeSpecialPage( '0-7475-3269-9', null, 'qqx' );
+ $this->assertNotContains( '(booksources-invalid-isbn)', $html );
+ $this->assertContains( '(booksources-text)', $html );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialEditWatchlistTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialEditWatchlistTest.php
new file mode 100644
index 00000000..05a63dbc
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialEditWatchlistTest.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @author Addshore
+ *
+ * @group Database
+ *
+ * @covers SpecialEditWatchlist
+ */
+class SpecialEditWatchlistTest extends SpecialPageTestBase {
+
+ /**
+ * Returns a new instance of the special page under test.
+ *
+ * @return SpecialPage
+ */
+ protected function newSpecialPage() {
+ return new SpecialEditWatchlist();
+ }
+
+ public function testNotLoggedIn_throwsException() {
+ $this->setExpectedException( UserNotLoggedIn::class );
+ $this->executeSpecialPage();
+ }
+
+ public function testRootPage_displaysExplanationMessage() {
+ $user = new TestUser( __METHOD__ );
+ list( $html, ) = $this->executeSpecialPage( '', null, 'qqx', $user->getUser() );
+ $this->assertContains( '(watchlistedit-normal-explain)', $html );
+ }
+
+ public function testClearPage_hasClearButtonForm() {
+ $user = new TestUser( __METHOD__ );
+ list( $html, ) = $this->executeSpecialPage( 'clear', null, 'qqx', $user->getUser() );
+ $this->assertRegExp(
+ '/<form class="mw-htmlform" action=".*?Special:EditWatchlist\/clear" method="post">/',
+ $html
+ );
+ }
+
+ public function testEditRawPage_hasTitlesBox() {
+ $user = new TestUser( __METHOD__ );
+ list( $html, ) = $this->executeSpecialPage( 'raw', null, 'qqx', $user->getUser() );
+ $this->assertContains(
+ '<textarea id="mw-input-wpTitles"',
+ $html
+ );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialMIMESearchTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialMIMESearchTest.php
new file mode 100644
index 00000000..4ecb813f
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialMIMESearchTest.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * @group Database
+ * @covers MIMEsearchPage
+ */
+class SpecialMIMESearchTest extends MediaWikiTestCase {
+
+ /** @var MIMEsearchPage */
+ private $page;
+
+ function setUp() {
+ $this->page = new MIMEsearchPage;
+ $context = new RequestContext();
+ $context->setTitle( Title::makeTitle( NS_SPECIAL, 'MIMESearch' ) );
+ $context->setRequest( new FauxRequest() );
+ $this->page->setContext( $context );
+
+ parent::setUp();
+ }
+
+ /**
+ * @dataProvider providerMimeFiltering
+ * @param string $par Subpage for special page
+ * @param string $major Major MIME type we expect to look for
+ * @param string $minor Minor MIME type we expect to look for
+ */
+ function testMimeFiltering( $par, $major, $minor ) {
+ $this->page->run( $par );
+ $qi = $this->page->getQueryInfo();
+ $this->assertEquals( $qi['conds']['img_major_mime'], $major );
+ if ( $minor !== null ) {
+ $this->assertEquals( $qi['conds']['img_minor_mime'], $minor );
+ } else {
+ $this->assertArrayNotHasKey( 'img_minor_mime', $qi['conds'] );
+ }
+ $this->assertContains( 'image', $qi['tables'] );
+ }
+
+ function providerMimeFiltering() {
+ return [
+ [ 'image/gif', 'image', 'gif' ],
+ [ 'image/png', 'image', 'png' ],
+ [ 'application/pdf', 'application', 'pdf' ],
+ [ 'image/*', 'image', null ],
+ [ 'multipart/*', 'multipart', null ],
+ ];
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialMyLanguageTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialMyLanguageTest.php
new file mode 100644
index 00000000..84fa71a2
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialMyLanguageTest.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @group Database
+ * @covers SpecialMyLanguage
+ */
+class SpecialMyLanguageTest extends MediaWikiTestCase {
+ public function addDBDataOnce() {
+ $titles = [
+ 'Page/Another',
+ 'Page/Another/ar',
+ 'Page/Another/en',
+ 'Page/Another/ru',
+ 'Page/Another/zh-hans',
+ ];
+ foreach ( $titles as $title ) {
+ $page = WikiPage::factory( Title::newFromText( $title ) );
+ if ( $page->getId() == 0 ) {
+ $page->doEditContent(
+ new WikitextContent( 'UTContent' ),
+ 'UTPageSummary',
+ EDIT_NEW,
+ false,
+ User::newFromName( 'UTSysop' ) );
+ }
+ }
+ }
+
+ /**
+ * @covers SpecialMyLanguage::findTitle
+ * @dataProvider provideFindTitle
+ * @param string $expected
+ * @param string $subpage
+ * @param string $langCode
+ * @param string $userLang
+ */
+ public function testFindTitle( $expected, $subpage, $langCode, $userLang ) {
+ $this->setMwGlobals( 'wgLanguageCode', $langCode );
+ $special = new SpecialMyLanguage();
+ $special->getContext()->setLanguage( $userLang );
+ // Test with subpages both enabled and disabled
+ $this->mergeMwGlobalArrayValue( 'wgNamespacesWithSubpages', [ NS_MAIN => true ] );
+ $this->assertTitle( $expected, $special->findTitle( $subpage ) );
+ $this->mergeMwGlobalArrayValue( 'wgNamespacesWithSubpages', [ NS_MAIN => false ] );
+ $this->assertTitle( $expected, $special->findTitle( $subpage ) );
+ }
+
+ /**
+ * @param string $expected
+ * @param Title|null $title
+ */
+ private function assertTitle( $expected, $title ) {
+ if ( $title ) {
+ $title = $title->getPrefixedText();
+ }
+ $this->assertEquals( $expected, $title );
+ }
+
+ public static function provideFindTitle() {
+ // See addDBDataOnce() for page declarations
+ return [
+ // [ $expected, $subpage, $langCode, $userLang ]
+ [ null, '::Fail', 'en', 'en' ],
+ [ 'Page/Another', 'Page/Another/en', 'en', 'en' ],
+ [ 'Page/Another', 'Page/Another', 'en', 'en' ],
+ [ 'Page/Another/ru', 'Page/Another', 'en', 'ru' ],
+ [ 'Page/Another', 'Page/Another', 'en', 'es' ],
+ [ 'Page/Another/zh-hans', 'Page/Another', 'en', 'zh-hans' ],
+ [ 'Page/Another/zh-hans', 'Page/Another', 'en', 'zh-mo' ],
+ [ 'Page/Another/en', 'Page/Another', 'de', 'es' ],
+ [ 'Page/Another/ar', 'Page/Another', 'en', 'ar' ],
+ [ 'Page/Another/ar', 'Page/Another', 'en', 'arz' ],
+ [ 'Page/Another/ar', 'Page/Another/de', 'en', 'arz' ],
+ [ 'Page/Another/ru', 'Page/Another/ru', 'en', 'arz' ],
+ [ 'Page/Another/ar', 'Page/Another/ru', 'en', 'ar' ],
+ ];
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialPageDataTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialPageDataTest.php
new file mode 100644
index 00000000..40754063
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialPageDataTest.php
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * @covers SpecialPageData
+ * @group Database
+ * @group SpecialPage
+ *
+ * @author Daniel Kinzler
+ */
+class SpecialPageDataTest extends SpecialPageTestBase {
+
+ protected function newSpecialPage() {
+ $page = new SpecialPageData();
+
+ // why is this needed?
+ $page->getContext()->setOutput( new OutputPage( $page->getContext() ) );
+
+ $page->setRequestHandler( new PageDataRequestHandler() );
+
+ return $page;
+ }
+
+ public function provideExecute() {
+ $cases = [];
+
+ $cases['Empty request'] = [ '', [], [], '!!', 200 ];
+
+ $cases['Only title specified'] = [
+ '',
+ [ 'target' => 'Helsinki' ],
+ [],
+ '!!',
+ 303,
+ [ 'Location' => '!.+!' ]
+ ];
+
+ $cases['Accept only HTML'] = [
+ '',
+ [ 'target' => 'Helsinki' ],
+ [ 'Accept' => 'text/HTML' ],
+ '!!',
+ 303,
+ [ 'Location' => '!Helsinki$!' ]
+ ];
+
+ $cases['Accept only HTML with revid'] = [
+ '',
+ [
+ 'target' => 'Helsinki',
+ 'revision' => '4242',
+ ],
+ [ 'Accept' => 'text/HTML' ],
+ '!!',
+ 303,
+ [ 'Location' => '!Helsinki(\?|&)oldid=4242!' ]
+ ];
+
+ $cases['Nothing specified'] = [
+ 'main/Helsinki',
+ [],
+ [],
+ '!!',
+ 303,
+ [ 'Location' => '!Helsinki&action=raw!' ]
+ ];
+
+ $cases['Nothing specified'] = [
+ '/Helsinki',
+ [],
+ [],
+ '!!',
+ 303,
+ [ 'Location' => '!Helsinki&action=raw!' ]
+ ];
+
+ $cases['Invalid Accept header'] = [
+ 'main/Helsinki',
+ [],
+ [ 'Accept' => 'text/foobar' ],
+ '!!',
+ 406,
+ [],
+ ];
+
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provideExecute
+ *
+ * @param string $subpage The subpage to request (or '')
+ * @param array $params Request parameters
+ * @param array $headers Request headers
+ * @param string $expRegExp Regex to match the output against.
+ * @param int $expCode Expected HTTP status code
+ * @param array $expHeaders Expected HTTP response headers
+ */
+ public function testExecute(
+ $subpage,
+ array $params,
+ array $headers,
+ $expRegExp,
+ $expCode = 200,
+ array $expHeaders = []
+ ) {
+ $request = new FauxRequest( $params );
+ $request->response()->header( 'Status: 200 OK', true, 200 ); // init/reset
+
+ foreach ( $headers as $name => $value ) {
+ $request->setHeader( strtoupper( $name ), $value );
+ }
+
+ try {
+ /* @var FauxResponse $response */
+ list( $output, $response ) = $this->executeSpecialPage( $subpage, $request );
+
+ $this->assertEquals( $expCode, $response->getStatusCode(), "status code" );
+ $this->assertRegExp( $expRegExp, $output, "output" );
+
+ foreach ( $expHeaders as $name => $exp ) {
+ $value = $response->getHeader( $name );
+ $this->assertNotNull( $value, "header: $name" );
+ $this->assertInternalType( 'string', $value, "header: $name" );
+ $this->assertRegExp( $exp, $value, "header: $name" );
+ }
+ } catch ( HttpError $e ) {
+ $this->assertEquals( $expCode, $e->getStatusCode(), "status code" );
+ $this->assertRegExp( $expRegExp, $e->getHTML(), "error output" );
+ }
+ }
+
+ public function testSpecialPageWithoutParameters() {
+ $this->setContentLang( Language::factory( 'en' ) );
+ $request = new FauxRequest();
+ $request->response()->header( 'Status: 200 OK', true, 200 ); // init/reset
+
+ list( $output, ) = $this->executeSpecialPage( '', $request );
+
+ $this->assertContains(
+ "Content negotiation applies based on your client's Accept header.",
+ $output,
+ "output"
+ );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialPageExecutor.php b/www/wiki/tests/phpunit/includes/specials/SpecialPageExecutor.php
new file mode 100644
index 00000000..e7cfca7f
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialPageExecutor.php
@@ -0,0 +1,129 @@
+<?php
+
+/**
+ * @author Addshore
+ *
+ * @since 1.27
+ */
+class SpecialPageExecutor {
+
+ /**
+ * @param SpecialPage $page The special page to execute
+ * @param string $subPage The subpage parameter to call the page with
+ * @param WebRequest|null $request Web request that may contain URL parameters, etc
+ * @param Language|string|null $language The language which should be used in the context
+ * @param User|null $user The user which should be used in the context of this special page
+ *
+ * @throws Exception
+ * @return array [ string, WebResponse ] A two-elements array containing the HTML output
+ * generated by the special page as well as the response object.
+ */
+ public function executeSpecialPage(
+ SpecialPage $page,
+ $subPage = '',
+ WebRequest $request = null,
+ $language = null,
+ User $user = null
+ ) {
+ $context = $this->newContext( $request, $language, $user );
+
+ $output = new OutputPage( $context );
+ $context->setOutput( $output );
+
+ $page->setContext( $context );
+ $output->setTitle( $page->getPageTitle() );
+
+ $html = $this->getHTMLFromSpecialPage( $page, $subPage );
+ $response = $context->getRequest()->response();
+
+ if ( $response instanceof FauxResponse ) {
+ $code = $response->getStatusCode();
+
+ if ( $code > 0 ) {
+ $response->header( 'Status: ' . $code . ' ' . HttpStatus::getMessage( $code ) );
+ }
+ }
+
+ return [ $html, $response ];
+ }
+
+ /**
+ * @param WebRequest|null $request
+ * @param Language|string|null $language
+ * @param User|null $user
+ *
+ * @return DerivativeContext
+ */
+ private function newContext(
+ WebRequest $request = null,
+ $language = null,
+ User $user = null
+ ) {
+ $context = new DerivativeContext( RequestContext::getMain() );
+
+ $context->setRequest( $request ?: new FauxRequest() );
+
+ if ( $language !== null ) {
+ $context->setLanguage( $language );
+ }
+
+ if ( $user !== null ) {
+ $context->setUser( $user );
+ }
+
+ $this->setEditTokenFromUser( $context );
+
+ return $context;
+ }
+
+ /**
+ * If we are trying to edit and no token is set, supply one.
+ *
+ * @param DerivativeContext $context
+ */
+ private function setEditTokenFromUser( DerivativeContext $context ) {
+ $request = $context->getRequest();
+
+ // Edits via GET are a security issue and should not succeed. On the other hand, not all
+ // POST requests are edits, but should ignore unused parameters.
+ if ( !$request->getCheck( 'wpEditToken' ) && $request->wasPosted() ) {
+ $request->setVal( 'wpEditToken', $context->getUser()->getEditToken() );
+ }
+ }
+
+ /**
+ * @param SpecialPage $page
+ * @param string $subPage
+ *
+ * @throws Exception
+ * @return string HTML
+ */
+ private function getHTMLFromSpecialPage( SpecialPage $page, $subPage ) {
+ ob_start();
+
+ try {
+ $page->execute( $subPage );
+
+ $output = $page->getOutput();
+
+ if ( $output->getRedirect() !== '' ) {
+ $output->output();
+ $html = ob_get_contents();
+ } elseif ( $output->isDisabled() ) {
+ $html = ob_get_contents();
+ } else {
+ $html = $output->getHTML();
+ }
+ } catch ( Exception $ex ) {
+ ob_end_clean();
+
+ // Re-throw exception after "finally" handling because PHP 5.3 doesn't have "finally".
+ throw $ex;
+ }
+
+ ob_end_clean();
+
+ return $html;
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialPageTestBase.php b/www/wiki/tests/phpunit/includes/specials/SpecialPageTestBase.php
new file mode 100644
index 00000000..274a23c4
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialPageTestBase.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * Base class for testing special pages.
+ *
+ * @since 1.26
+ *
+ * @license GNU GPL v2+
+ * @author Jeroen De Dauw < jeroendedauw@gmail.com >
+ * @author Daniel Kinzler
+ * @author Addshore
+ * @author Thiemo Kreuz
+ */
+abstract class SpecialPageTestBase extends MediaWikiTestCase {
+
+ private $obLevel;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->obLevel = ob_get_level();
+ }
+
+ protected function tearDown() {
+ $obLevel = ob_get_level();
+
+ while ( ob_get_level() > $this->obLevel ) {
+ ob_end_clean();
+ }
+
+ if ( $obLevel !== $this->obLevel ) {
+ $this->fail(
+ "Test changed output buffer level: was {$this->obLevel} before test, but $obLevel after test."
+ );
+ }
+
+ parent::tearDown();
+ }
+
+ /**
+ * Returns a new instance of the special page under test.
+ *
+ * @return SpecialPage
+ */
+ abstract protected function newSpecialPage();
+
+ /**
+ * @param string $subPage The subpage parameter to call the page with
+ * @param WebRequest|null $request Web request that may contain URL parameters, etc
+ * @param Language|string|null $language The language which should be used in the context
+ * @param User|null $user The user which should be used in the context of this special page
+ *
+ * @throws Exception
+ * @return array [ string, WebResponse ] A two-elements array containing the HTML output
+ * generated by the special page as well as the response object.
+ */
+ protected function executeSpecialPage(
+ $subPage = '',
+ WebRequest $request = null,
+ $language = null,
+ User $user = null
+ ) {
+ return ( new SpecialPageExecutor() )->executeSpecialPage(
+ $this->newSpecialPage(),
+ $subPage,
+ $request,
+ $language,
+ $user
+ );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialPreferencesTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialPreferencesTest.php
new file mode 100644
index 00000000..bdfbb62e
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialPreferencesTest.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * Test class for SpecialPreferences class.
+ *
+ * Copyright © 2013, Antoine Musso
+ * Copyright © 2013, Wikimedia Foundation Inc.
+ */
+
+/**
+ * @group Preferences
+ * @group Database
+ *
+ * @covers SpecialPreferences
+ */
+class SpecialPreferencesTest extends MediaWikiTestCase {
+
+ /**
+ * Make sure a nickname which is longer than $wgMaxSigChars
+ * is not throwing a fatal error.
+ *
+ * Test specifications by Alexandre "ialex" Emsenhuber.
+ * @todo give this test a real name explaining what is being tested here
+ */
+ public function testBug41337() {
+ // Set a low limit
+ $this->setMwGlobals( 'wgMaxSigChars', 2 );
+
+ $user = $this->createMock( User::class );
+ $user->expects( $this->any() )
+ ->method( 'isAnon' )
+ ->will( $this->returnValue( false ) );
+
+ # Yeah foreach requires an array, not NULL =(
+ $user->expects( $this->any() )
+ ->method( 'getEffectiveGroups' )
+ ->will( $this->returnValue( [] ) );
+
+ # The mocked user has a long nickname
+ $user->expects( $this->any() )
+ ->method( 'getOption' )
+ ->will( $this->returnValueMap( [
+ [ 'nickname', null, false, 'superlongnickname' ],
+ ]
+ ) );
+
+ # Forge a request to call the special page
+ $context = new RequestContext();
+ $context->setRequest( new FauxRequest() );
+ $context->setUser( $user );
+ $context->setTitle( Title::newFromText( 'Test' ) );
+
+ # Do the call, should not spurt a fatal error.
+ $special = new SpecialPreferences();
+ $special->setContext( $context );
+ $this->assertNull( $special->execute( [] ) );
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialRecentchangesTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialRecentchangesTest.php
new file mode 100644
index 00000000..0b6962d5
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialRecentchangesTest.php
@@ -0,0 +1,52 @@
+<?php
+
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * Test class for SpecialRecentchanges class
+ *
+ * @group Database
+ *
+ * @covers SpecialRecentChanges
+ */
+class SpecialRecentchangesTest extends AbstractChangesListSpecialPageTestCase {
+ protected function getPage() {
+ return TestingAccessWrapper::newFromObject(
+ new SpecialRecentChanges
+ );
+ }
+
+ // Below providers should only be for features specific to
+ // RecentChanges. Otherwise, it should go in ChangesListSpecialPageTest
+
+ public function provideParseParameters() {
+ return [
+ [ 'limit=123', [ 'limit' => '123' ] ],
+
+ [ '234', [ 'limit' => '234' ] ],
+
+ [ 'days=3', [ 'days' => '3' ] ],
+
+ [ 'days=0.25', [ 'days' => '0.25' ] ],
+
+ [ 'namespace=5', [ 'namespace' => '5' ] ],
+
+ [ 'namespace=5|3', [ 'namespace' => '5|3' ] ],
+
+ [ 'tagfilter=foo', [ 'tagfilter' => 'foo' ] ],
+
+ [ 'tagfilter=foo;bar', [ 'tagfilter' => 'foo;bar' ] ],
+ ];
+ }
+
+ public function validateOptionsProvider() {
+ return [
+ [
+ // hidebots=1 is default for Special:RecentChanges
+ [ 'hideanons' => 1, 'hideliu' => 1 ],
+ true,
+ [ 'hideliu' => 1 ],
+ ],
+ ];
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialSearchTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialSearchTest.php
new file mode 100644
index 00000000..f0a57266
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialSearchTest.php
@@ -0,0 +1,296 @@
+<?php
+use MediaWiki\MediaWikiServices;
+
+/**
+ * Test class for SpecialSearch class
+ * Copyright © 2012, Antoine Musso
+ *
+ * @author Antoine Musso
+ * @group Database
+ */
+class SpecialSearchTest extends MediaWikiTestCase {
+
+ /**
+ * @covers SpecialSearch::load
+ * @dataProvider provideSearchOptionsTests
+ * @param array $requested Request parameters. For example:
+ * array( 'ns5' => true, 'ns6' => true). Null to use default options.
+ * @param array $userOptions User options to test with. For example:
+ * array('searchNs5' => 1 );. Null to use default options.
+ * @param string $expectedProfile An expected search profile name
+ * @param array $expectedNS Expected namespaces
+ * @param string $message
+ */
+ public function testProfileAndNamespaceLoading( $requested, $userOptions,
+ $expectedProfile, $expectedNS, $message = 'Profile name and namespaces mismatches!'
+ ) {
+ $context = new RequestContext;
+ $context->setUser(
+ $this->newUserWithSearchNS( $userOptions )
+ );
+ /*
+ $context->setRequest( new FauxRequest( [
+ 'ns5'=>true,
+ 'ns6'=>true,
+ ] ));
+ */
+ $context->setRequest( new FauxRequest( $requested ) );
+ $search = new SpecialSearch();
+ $search->setContext( $context );
+ $search->load();
+
+ /**
+ * Verify profile name and namespace in the same assertion to make
+ * sure we will be able to fully compare the above code. PHPUnit stop
+ * after an assertion fail.
+ */
+ $this->assertEquals(
+ [ /** Expected: */
+ 'ProfileName' => $expectedProfile,
+ 'Namespaces' => $expectedNS,
+ ],
+ [ /** Actual: */
+ 'ProfileName' => $search->getProfile(),
+ 'Namespaces' => $search->getNamespaces(),
+ ],
+ $message
+ );
+ }
+
+ public static function provideSearchOptionsTests() {
+ $defaultNS = MediaWikiServices::getInstance()->getSearchEngineConfig()->defaultNamespaces();
+ $EMPTY_REQUEST = [];
+ $NO_USER_PREF = null;
+
+ return [
+ /**
+ * Parameters:
+ * <Web Request>, <User options>
+ * Followed by expected values:
+ * <ProfileName>, <NSList>
+ * Then an optional message.
+ */
+ [
+ $EMPTY_REQUEST, $NO_USER_PREF,
+ 'default', $defaultNS,
+ 'T35270: No request nor user preferences should give default profile'
+ ],
+ [
+ [ 'ns5' => 1 ], $NO_USER_PREF,
+ 'advanced', [ 5 ],
+ 'Web request with specific NS should override user preference'
+ ],
+ [
+ $EMPTY_REQUEST, [
+ 'searchNs2' => 1,
+ 'searchNs14' => 1,
+ ] + array_fill_keys( array_map( function ( $ns ) {
+ return "searchNs$ns";
+ }, $defaultNS ), 0 ),
+ 'advanced', [ 2, 14 ],
+ 'T35583: search with no option should honor User search preferences'
+ . ' and have all other namespace disabled'
+ ],
+ ];
+ }
+
+ /**
+ * Helper to create a new User object with given options
+ * User remains anonymous though
+ * @param array|null $opt
+ */
+ function newUserWithSearchNS( $opt = null ) {
+ $u = User::newFromId( 0 );
+ if ( $opt === null ) {
+ return $u;
+ }
+ foreach ( $opt as $name => $value ) {
+ $u->setOption( $name, $value );
+ }
+
+ return $u;
+ }
+
+ /**
+ * Verify we do not expand search term in <title> on search result page
+ * https://gerrit.wikimedia.org/r/4841
+ */
+ public function testSearchTermIsNotExpanded() {
+ $this->setMwGlobals( [
+ 'wgSearchType' => null,
+ ] );
+
+ # Initialize [[Special::Search]]
+ $ctx = new RequestContext();
+ $term = '{{SITENAME}}';
+ $ctx->setRequest( new FauxRequest( [ 'search' => $term, 'fulltext' => 1 ] ) );
+ $ctx->setTitle( Title::newFromText( 'Special:Search' ) );
+ $search = new SpecialSearch();
+ $search->setContext( $ctx );
+
+ # Simulate a user searching for a given term
+ $search->execute( '' );
+
+ # Lookup the HTML page title set for that page
+ $pageTitle = $search
+ ->getContext()
+ ->getOutput()
+ ->getHTMLTitle();
+
+ # Compare :-]
+ $this->assertRegExp(
+ '/' . preg_quote( $term, '/' ) . '/',
+ $pageTitle,
+ "Search term '{$term}' should not be expanded in Special:Search <title>"
+ );
+ }
+
+ public function provideRewriteQueryWithSuggestion() {
+ return [
+ [
+ 'With suggestion and no rewritten query shows did you mean',
+ '/Did you mean: <a[^>]+>first suggestion/',
+ 'first suggestion',
+ null,
+ [ Title::newMainPage() ]
+ ],
+
+ [
+ 'With rewritten query informs user of change',
+ '/Showing results for <a[^>]+>first suggestion/',
+ 'asdf',
+ 'first suggestion',
+ [ Title::newMainPage() ]
+ ],
+
+ [
+ 'When both queries have no results user gets no results',
+ '/There were no results matching the query/',
+ 'first suggestion',
+ 'first suggestion',
+ []
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideRewriteQueryWithSuggestion
+ */
+ public function testRewriteQueryWithSuggestion(
+ $message,
+ $expectRegex,
+ $suggestion,
+ $rewrittenQuery,
+ array $resultTitles
+ ) {
+ $results = array_map( function ( $title ) {
+ return SearchResult::newFromTitle( $title );
+ }, $resultTitles );
+
+ $searchResults = new SpecialSearchTestMockResultSet(
+ $suggestion,
+ $rewrittenQuery,
+ $results
+ );
+
+ $mockSearchEngine = $this->mockSearchEngine( $searchResults );
+ $search = $this->getMockBuilder( SpecialSearch::class )
+ ->setMethods( [ 'getSearchEngine' ] )
+ ->getMock();
+ $search->expects( $this->any() )
+ ->method( 'getSearchEngine' )
+ ->will( $this->returnValue( $mockSearchEngine ) );
+
+ $search->getContext()->setTitle( Title::makeTitle( NS_SPECIAL, 'Search' ) );
+ $search->getContext()->setLanguage( Language::factory( 'en' ) );
+ $search->load();
+ $search->showResults( 'this is a fake search' );
+
+ $html = $search->getContext()->getOutput()->getHTML();
+ foreach ( (array)$expectRegex as $regex ) {
+ $this->assertRegExp( $regex, $html, $message );
+ }
+ }
+
+ protected function mockSearchEngine( $results ) {
+ $mock = $this->getMockBuilder( SearchEngine::class )
+ ->setMethods( [ 'searchText', 'searchTitle' ] )
+ ->getMock();
+
+ $mock->expects( $this->any() )
+ ->method( 'searchText' )
+ ->will( $this->returnValue( $results ) );
+
+ return $mock;
+ }
+
+ public function testSubPageRedirect() {
+ $this->setMwGlobals( [
+ 'wgScript' => '/w/index.php',
+ ] );
+
+ $ctx = new RequestContext;
+ $sp = Title::newFromText( 'Special:Search/foo_bar' );
+ SpecialPageFactory::executePath( $sp, $ctx );
+ $url = $ctx->getOutput()->getRedirect();
+ // some older versions of hhvm have a bug that doesn't parse relative
+ // urls with a port, so help it out a little bit.
+ // https://github.com/facebook/hhvm/issues/7136
+ $url = wfExpandUrl( $url, PROTO_CURRENT );
+
+ $parts = parse_url( $url );
+ $this->assertEquals( '/w/index.php', $parts['path'] );
+ parse_str( $parts['query'], $query );
+ $this->assertEquals( 'Special:Search', $query['title'] );
+ $this->assertEquals( 'foo bar', $query['search'] );
+ }
+}
+
+class SpecialSearchTestMockResultSet extends SearchResultSet {
+ protected $results;
+ protected $suggestion;
+
+ public function __construct(
+ $suggestion = null,
+ $rewrittenQuery = null,
+ array $results = [],
+ $containedSyntax = false
+ ) {
+ $this->suggestion = $suggestion;
+ $this->rewrittenQuery = $rewrittenQuery;
+ $this->results = $results;
+ $this->containedSyntax = $containedSyntax;
+ }
+
+ public function numRows() {
+ return count( $this->results );
+ }
+
+ public function getTotalHits() {
+ return $this->numRows();
+ }
+
+ public function hasSuggestion() {
+ return $this->suggestion !== null;
+ }
+
+ public function getSuggestionQuery() {
+ return $this->suggestion;
+ }
+
+ public function getSuggestionSnippet() {
+ return $this->suggestion;
+ }
+
+ public function hasRewrittenQuery() {
+ return $this->rewrittenQuery !== null;
+ }
+
+ public function getQueryAfterRewrite() {
+ return $this->rewrittenQuery;
+ }
+
+ public function getQueryAfterRewriteSnippet() {
+ return htmlspecialchars( $this->rewrittenQuery );
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialShortpagesTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialShortpagesTest.php
new file mode 100644
index 00000000..f799b115
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialShortpagesTest.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Test class for SpecialShortpages class
+ *
+ * @since 1.30
+ *
+ * @license GNU GPL v2+
+ */
+class SpecialShortpagesTest extends MediaWikiTestCase {
+
+ /**
+ * @dataProvider provideGetQueryInfoRespectsContentNs
+ * @covers ShortPagesPage::getQueryInfo()
+ */
+ public function testGetQueryInfoRespectsContentNS( $contentNS, $blacklistNS, $expectedNS ) {
+ $this->setMwGlobals( [
+ 'wgShortPagesNamespaceBlacklist' => $blacklistNS,
+ 'wgContentNamespaces' => $contentNS
+ ] );
+ $this->setTemporaryHook( 'ShortPagesQuery', function () {
+ // empty hook handler
+ } );
+
+ $page = new ShortPagesPage();
+ $queryInfo = $page->getQueryInfo();
+
+ $this->assertArrayHasKey( 'conds', $queryInfo );
+ $this->assertArrayHasKey( 'page_namespace', $queryInfo[ 'conds' ] );
+ $this->assertEquals( $expectedNS, $queryInfo[ 'conds' ][ 'page_namespace' ] );
+ }
+
+ public function provideGetQueryInfoRespectsContentNs() {
+ return [
+ [ [ NS_MAIN, NS_FILE ], [], [ NS_MAIN, NS_FILE ] ],
+ [ [ NS_MAIN, NS_TALK ], [ NS_FILE ], [ NS_MAIN, NS_TALK ] ],
+ [ [ NS_MAIN, NS_FILE ], [ NS_FILE ], [ NS_MAIN ] ],
+ // NS_MAIN namespace is always forced
+ [ [], [ NS_FILE ], [ NS_MAIN ] ]
+ ];
+ }
+
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialUncategorizedcategoriesTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialUncategorizedcategoriesTest.php
new file mode 100644
index 00000000..80bd365f
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialUncategorizedcategoriesTest.php
@@ -0,0 +1,63 @@
+<?php
+/**
+ * Tests for Special:Uncategorizedcategories
+ */
+class UncategorizedCategoriesPageTest extends MediaWikiTestCase {
+ /**
+ * @dataProvider provideTestGetQueryInfoData
+ * @covers UncategorizedCategoriesPage::getQueryInfo
+ */
+ public function testGetQueryInfo( $msgContent, $expected ) {
+ $msg = new RawMessage( $msgContent );
+ $mockContext = $this->getMockBuilder( RequestContext::class )->getMock();
+ $mockContext->method( 'msg' )->willReturn( $msg );
+ $special = new UncategorizedCategoriesPage();
+ $special->setContext( $mockContext );
+ $this->assertEquals( [
+ 'tables' => [
+ 0 => 'page',
+ 1 => 'categorylinks',
+ ],
+ 'fields' => [
+ 'namespace' => 'page_namespace',
+ 'title' => 'page_title',
+ 'value' => 'page_title',
+ ],
+ 'conds' => [
+ 0 => 'cl_from IS NULL',
+ 'page_namespace' => 14,
+ 'page_is_redirect' => 0,
+ ] + $expected,
+ 'join_conds' => [
+ 'categorylinks' => [
+ 0 => 'LEFT JOIN',
+ 1 => 'cl_from = page_id',
+ ],
+ ],
+ ], $special->getQueryInfo() );
+ }
+
+ public function provideTestGetQueryInfoData() {
+ return [
+ [
+ "* Stubs\n* Test\n* *\n* * test123",
+ [ 1 => "page_title not in ( 'Stubs','Test','*','*_test123' )" ]
+ ],
+ [
+ "Stubs\n* Test\n* *\n* * test123",
+ [ 1 => "page_title not in ( 'Test','*','*_test123' )" ]
+ ],
+ [
+ "* StubsTest\n* *\n* * test123",
+ [ 1 => "page_title not in ( 'StubsTest','*','*_test123' )" ]
+ ],
+ [ "", [] ],
+ [ "\n\n\n", [] ],
+ [ "\n", [] ],
+ [ "Test\n*Test2", [ 1 => "page_title not in ( 'Test2' )" ] ],
+ [ "Test", [] ],
+ [ "*Test\nTest2", [ 1 => "page_title not in ( 'Test' )" ] ],
+ [ "Test\nTest2", [] ],
+ ];
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialUploadTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialUploadTest.php
new file mode 100644
index 00000000..95026c18
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialUploadTest.php
@@ -0,0 +1,29 @@
+<?php
+
+class SpecialUploadTest extends MediaWikiTestCase {
+ /**
+ * @covers SpecialUpload::getInitialPageText
+ * @dataProvider provideGetInitialPageText
+ */
+ public function testGetInitialPageText( $expected, $inputParams ) {
+ $result = call_user_func_array( [ 'SpecialUpload', 'getInitialPageText' ], $inputParams );
+ $this->assertEquals( $expected, $result );
+ }
+
+ public function provideGetInitialPageText() {
+ return [
+ [
+ 'expect' => "== Summary ==\nthis is a test\n",
+ 'params' => [
+ 'this is a test'
+ ],
+ ],
+ [
+ 'expect' => "== Summary ==\nthis is a test\n",
+ 'params' => [
+ "== Summary ==\nthis is a test",
+ ],
+ ],
+ ];
+ }
+}
diff --git a/www/wiki/tests/phpunit/includes/specials/SpecialWatchlistTest.php b/www/wiki/tests/phpunit/includes/specials/SpecialWatchlistTest.php
new file mode 100644
index 00000000..5adbed81
--- /dev/null
+++ b/www/wiki/tests/phpunit/includes/specials/SpecialWatchlistTest.php
@@ -0,0 +1,192 @@
+<?php
+
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @author Addshore
+ *
+ * @group Database
+ *
+ * @covers SpecialWatchlist
+ */
+class SpecialWatchlistTest extends SpecialPageTestBase {
+ public function setUp() {
+ parent::setUp();
+
+ $this->setTemporaryHook(
+ 'ChangesListSpecialPageFilters',
+ null
+ );
+
+ $this->setTemporaryHook(
+ 'SpecialWatchlistQuery',
+ null
+ );
+
+ $this->setTemporaryHook(
+ 'ChangesListSpecialPageQuery',
+ null
+ );
+
+ $this->setMwGlobals(
+ 'wgDefaultUserOptions',
+ [
+ 'extendwatchlist' => 1,
+ 'watchlistdays' => 3.0,
+ 'watchlisthideanons' => 0,
+ 'watchlisthidebots' => 0,
+ 'watchlisthideliu' => 0,
+ 'watchlisthideminor' => 0,
+ 'watchlisthideown' => 0,
+ 'watchlisthidepatrolled' => 0,
+ 'watchlisthidecategorization' => 1,
+ 'watchlistreloadautomatically' => 0,
+ 'watchlistunwatchlinks' => 0,
+ ]
+ );
+ }
+
+ /**
+ * Returns a new instance of the special page under test.
+ *
+ * @return SpecialPage
+ */
+ protected function newSpecialPage() {
+ return new SpecialWatchlist();
+ }
+
+ public function testNotLoggedIn_throwsException() {
+ $this->setExpectedException( UserNotLoggedIn::class );
+ $this->executeSpecialPage();
+ }
+
+ public function testUserWithNoWatchedItems_displaysNoWatchlistMessage() {
+ $user = new TestUser( __METHOD__ );
+ list( $html, ) = $this->executeSpecialPage( '', null, 'qqx', $user->getUser() );
+ $this->assertContains( '(nowatchlist)', $html );
+ }
+
+ /**
+ * @dataProvider provideFetchOptionsFromRequest
+ */
+ public function testFetchOptionsFromRequest( $expectedValues, $preferences, $inputParams ) {
+ $page = TestingAccessWrapper::newFromObject(
+ $this->newSpecialPage()
+ );
+
+ $context = new DerivativeContext( $page->getContext() );
+
+ $fauxRequest = new FauxRequest( $inputParams, /* $wasPosted= */ false );
+ $user = $this->getTestUser()->getUser();
+
+ foreach ( $preferences as $key => $value ) {
+ $user->setOption( $key, $value );
+ }
+
+ $context->setRequest( $fauxRequest );
+ $context->setUser( $user );
+ $page->setContext( $context );
+
+ $page->registerFilters();
+ $formOptions = $page->getDefaultOptions();
+ $page->fetchOptionsFromRequest( $formOptions );
+
+ $this->assertArrayEquals(
+ $expectedValues,
+ $formOptions->getAllValues(),
+ /* $ordered= */ false,
+ /* $named= */ true
+ );
+ }
+
+ public function provideFetchOptionsFromRequest() {
+ // $defaults and $allFalse are just to make the expected values below
+ // shorter by hiding the background.
+
+ $page = TestingAccessWrapper::newFromObject(
+ $this->newSpecialPage()
+ );
+
+ $this->setTemporaryHook(
+ 'ChangesListSpecialPageFilters',
+ null
+ );
+
+ $page->registerFilters();
+
+ // Does not consider $preferences, just wiki's defaults
+ $wikiDefaults = $page->getDefaultOptions()->getAllValues();
+
+ $allFalse = $wikiDefaults;
+
+ foreach ( $allFalse as $key => &$value ) {
+ if ( $value === true ) {
+ $value = false;
+ }
+ }
+
+ // This is not exposed on the form (only in preferences) so it
+ // respects the preference.
+ $allFalse['extended'] = true;
+
+ return [
+ [
+ [
+ 'hideminor' => true,
+ ] + $wikiDefaults,
+ [],
+ [
+ 'hideMinor' => 1,
+ ],
+ ],
+
+ [
+ [
+ // First two same as prefs
+ 'hideminor' => true,
+ 'hidebots' => false,
+
+ // Second two overriden
+ 'hideanons' => false,
+ 'hideliu' => true,
+ 'userExpLevel' => 'registered'
+ ] + $wikiDefaults,
+ [
+ 'watchlisthideminor' => 1,
+ 'watchlisthidebots' => 0,
+
+ 'watchlisthideanons' => 1,
+ 'watchlisthideliu' => 0,
+ ],
+ [
+ 'hideanons' => 0,
+ 'hideliu' => 1,
+ ],
+ ],
+
+ // Defaults/preferences for form elements are entirely ignored for
+ // action=submit and omitted elements become false
+ [
+ [
+ 'hideminor' => false,
+ 'hidebots' => true,
+ 'hideanons' => false,
+ 'hideliu' => true,
+ 'userExpLevel' => 'unregistered'
+ ] + $allFalse,
+ [
+ 'watchlisthideminor' => 0,
+ 'watchlisthidebots' => 1,
+
+ 'watchlisthideanons' => 0,
+ 'watchlisthideliu' => 1,
+ ],
+ [
+ 'hidebots' => 1,
+ 'hideliu' => 1,
+ 'action' => 'submit',
+ ],
+ ],
+ ];
+ }
+}